MMR Match Berechnung funktioniert. Khorne und Slaanesh Regel sind eingefügt und Funktionieren. Liga Statistik Ansicht hat eine Rangliste bekommen. Match Eingabe sollte jetzt auf Mobile richtig Skalieren.
This commit is contained in:
parent
0f914f3117
commit
4da6c4c6be
|
|
@ -1 +1 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verzweifelter Regelvergesser","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel Nagel","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -375,4 +375,103 @@ def get_match_by_id(match_id):
|
|||
return None # Falls die ID nicht existiert
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
def get_days_since_last_game(player_id):
|
||||
"""
|
||||
Sucht das absolut letzte Spiel eines Spielers (über alle Systeme)
|
||||
und berechnet, wie viele Tage das her ist.
|
||||
"""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# MAX(last_played) sucht den absolut neuesten Zeitstempel aus allen Einträgen dieses Spielers
|
||||
query = """
|
||||
SELECT MAX(last_played)
|
||||
FROM player_game_statistic
|
||||
WHERE player_id = ?
|
||||
"""
|
||||
|
||||
cursor.execute(query, (player_id,))
|
||||
row = cursor.fetchone()
|
||||
connection.close()
|
||||
|
||||
# row[0] enthält jetzt unseren Zeitstempel (z.B. '2026-03-05 14:30:00')
|
||||
last_played_str = row[0] if row else None
|
||||
|
||||
# Sicherheitscheck: Hat der Spieler überhaupt schon Einträge?
|
||||
if not last_played_str:
|
||||
return None
|
||||
|
||||
try:
|
||||
# [:19] schneidet Millisekunden ab, damit das Format exakt passt.
|
||||
last_played_date = datetime.strptime(last_played_str[:19], '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 2. Die Differenz zu "Jetzt genau in diesem Moment" berechnen
|
||||
time_difference = datetime.now() - last_played_date
|
||||
|
||||
# 3. .days holt aus der Zeitdifferenz nur die reinen, vollen Tage heraus
|
||||
days_ago = time_difference.days
|
||||
|
||||
return {
|
||||
"date_string": last_played_str[:10], # Nur das Datum (YYYY-MM-DD) für die GUI
|
||||
"days_ago": days_ago # Die nackte Zahl zum Rechnen (z.B. 14)
|
||||
}
|
||||
|
||||
except ValueError:
|
||||
# Falls in der Datenbank mal ein kaputter String steht
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
def apply_match_to_player_statistic (player_id, gamesystem_id, mmr_change, scored_points):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# CURRENT_TIMESTAMP setzt die Uhrzeit für das "Letzte Spiel" automatisch auf genau JETZT.
|
||||
query = """
|
||||
UPDATE player_game_statistic
|
||||
SET
|
||||
mmr = mmr + ?,
|
||||
games_in_system = games_in_system + 1,
|
||||
points = points + ?,
|
||||
avv_points = (points + ?) / (games_in_system + 1),
|
||||
last_played = CURRENT_TIMESTAMP
|
||||
WHERE player_id = ? AND gamesystem_id = ?
|
||||
"""
|
||||
|
||||
# ACHTUNG: Wir müssen scored_points ZWEIMAL übergeben!
|
||||
# Einmal für die 'points' und einmal für die Berechnung der 'avv_points'.
|
||||
cursor.execute(query, (mmr_change, scored_points, scored_points, player_id, gamesystem_id))
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
def get_leaderboard(system_name):
|
||||
"""Holt alle Spieler eines Systems sortiert nach MMR für die Rangliste."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Wir holen ID, Namen, Discord und MMR, sortiert vom höchsten MMR zum niedrigsten
|
||||
query = """
|
||||
SELECT p.id, p.display_name, p.discord_name, stat.mmr
|
||||
FROM players p
|
||||
JOIN player_game_statistic stat ON p.id = stat.player_id
|
||||
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
|
||||
WHERE sys.name = ?
|
||||
ORDER BY stat.mmr DESC
|
||||
"""
|
||||
|
||||
cursor.execute(query, (system_name,))
|
||||
rows = cursor.fetchall()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ def init_db():
|
|||
player2_id INTEGER,
|
||||
score_player2 INTEGER,
|
||||
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
mmr_change_winner INTEGER,
|
||||
mmr_change_looser INTEGER,
|
||||
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id),
|
||||
FOREIGN KEY (player1_id) REFERENCES players (id),
|
||||
FOREIGN KEY (player2_id) REFERENCES players (id)
|
||||
|
|
@ -138,7 +140,7 @@ def seed_achievements():
|
|||
connection.commit()
|
||||
connection.close()
|
||||
print("Achievements angelegt.")
|
||||
seed_dummy_player()
|
||||
#seed_dummy_player()
|
||||
|
||||
|
||||
def seed_dummy_player():
|
||||
|
|
|
|||
|
|
@ -27,16 +27,56 @@ def setup_routes():
|
|||
|
||||
|
||||
# --- BLOCK 1 (2 Karten) ---
|
||||
with ui.card().classes("w-full"):
|
||||
with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-4"):
|
||||
# 1. Daten für die Rangliste holen
|
||||
leaderboard_data = data_api.get_leaderboard(systemname)
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
# 2. Tabelle vorbereiten und den EIGENEN Rang herausfinden
|
||||
table_rows = []
|
||||
my_rank = "-"
|
||||
|
||||
for index, player in enumerate(leaderboard_data):
|
||||
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:
|
||||
my_rank = current_rank
|
||||
|
||||
table_rows.append({
|
||||
'rank': current_rank,
|
||||
'trend': '➖', # Platzhalter für später
|
||||
'name': f"{player['display_name']} aka {player['discord_name']}",
|
||||
'mmr': player['mmr']
|
||||
})
|
||||
|
||||
table_columns = [
|
||||
{'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'},
|
||||
{'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'},
|
||||
{'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'},
|
||||
{'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'align': 'left'}
|
||||
]
|
||||
|
||||
# --- 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"):
|
||||
|
||||
# LINKE SEITE (Belegt 1 Spalte)
|
||||
# flex-col setzt die beiden Karten exakt übereinander
|
||||
with ui.column().classes("w-full gap-4"):
|
||||
|
||||
with ui.card().classes("w-full items-center justify-center text-center"):
|
||||
ui.label("MMR Punkte: ").classes('text-2xl font-bold')
|
||||
ui.label(str(stats["mmr"])).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("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("-").classes('text-4xl font-bold text-blue-100')
|
||||
# Hier tragen wir jetzt unsere gefundene Platzierung ein!
|
||||
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"):
|
||||
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')
|
||||
|
||||
|
||||
# --- BLOCK 2 (5 Karten) ---
|
||||
with ui.card().classes("w-full"):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from nicegui import ui, app
|
||||
from gui import gui_style
|
||||
from data import data_api
|
||||
from mmr_calculations import calc_match
|
||||
from match_calculations import calc_match
|
||||
|
||||
def setup_routes():
|
||||
|
||||
|
|
@ -11,70 +11,68 @@ def setup_routes():
|
|||
gui_style.apply_design()
|
||||
|
||||
# --- SICHERHEITS-CHECK ---
|
||||
# Prüfen, ob der User wirklich eingeloggt ist.
|
||||
if not app.storage.user.get('authenticated', False):
|
||||
ui.label('Access Denied. Please log in first.').classes('text-red-500')
|
||||
ui.button('Back to Home', on_click=lambda: ui.navigate.to('/'))
|
||||
return
|
||||
|
||||
with ui.card().classes('w-150 items-center mt-10 justify-center'):
|
||||
ui.label('Neues Spiel für '+ systemname + " eintragen").classes('text-2xl font-bold')
|
||||
# ÄNDERUNG: w-full (für Handy) + max-w-md (für PC) + mx-auto (Zentrieren) + p-6 (Innenabstand)
|
||||
with ui.card().classes('w-full max-w-md mx-auto items-center mt-10 p-6 shadow-xl'):
|
||||
|
||||
ui.space()
|
||||
# Text-Center hinzugefügt, falls der Systemname sehr lang ist und auf dem Handy umbricht
|
||||
ui.label(f'Neues Spiel für {systemname} eintragen').classes('text-2xl font-bold text-center mb-6')
|
||||
|
||||
ui.label("Meine Punkte:").classes('text-xl font-bold')
|
||||
ui.label("Meine Punkte:").classes('text-xl font-bold w-full text-left')
|
||||
|
||||
ui.space()
|
||||
# ÄNDERUNG: h-60 entfernt, stattdessen gap-6 (Abstand zwischen den Elementen)
|
||||
with ui.column().classes("w-full items-center gap-6"):
|
||||
|
||||
with ui.column().classes("w-full items-center h-60"):
|
||||
# 1. Daten aus der DB holen
|
||||
raw_players = data_api.get_all_players_from_system(systemname)
|
||||
my_id = app.storage.user.get('db_id')
|
||||
|
||||
# 3. Eine saubere Optionen-Liste (Dictionary) für NiceGUI bauen
|
||||
# 3. Eine saubere Optionen-Liste für NiceGUI bauen
|
||||
dropdown_options = {}
|
||||
|
||||
for p in raw_players:
|
||||
# FILTERN: Wenn die ID des Spielers MEINE EIGENE ID ist, überspringen wir ihn!
|
||||
if p['player_id'] == my_id:
|
||||
continue # Geht sofort zum nächsten Spieler in der Schleife
|
||||
|
||||
# Die ID wird der Schlüssel, der kombinierte Name wird der Anzeigetext
|
||||
# Ergebnis sieht z.B. so aus: { 2: "Dummy Mc DummDumm aka dummy_user" }
|
||||
continue
|
||||
dropdown_options[p['player_id']] = f"{p['display_name']} aka {p['discord_name']}"
|
||||
|
||||
# 4. Eigene Punkte eintragen
|
||||
p1_points = ui.slider(min= 0, max=100, value = 10).props("label-always")
|
||||
# ÄNDERUNG: .classes('w-full') hinzugefügt, damit der Slider sich anpasst
|
||||
p1_points = ui.slider(min=0, max=100, value=10).props("label-always").classes('w-full')
|
||||
|
||||
ui.space()
|
||||
ui.separator().classes('w-full mt-4') # Ein schöner Trennstrich für die Optik
|
||||
|
||||
# 5. Das Dropdown mit unseren sauberen Optionen füttern
|
||||
ui.label("Gegner:").classes('text-xl font-bold')
|
||||
opponent_select = ui.select(options=dropdown_options, label='Gegner auswählen').classes('w-64')
|
||||
p2_points = ui.slider(min= 0, max=100, value = 10).props("label-always")
|
||||
# 5. Dropdown und Gegner Punkte
|
||||
ui.label("Gegner:").classes('text-xl font-bold w-full text-left')
|
||||
|
||||
# ÄNDERUNG: w-64 durch w-full ersetzt
|
||||
opponent_select = ui.select(options=dropdown_options, label='Gegner auswählen').classes('w-full')
|
||||
|
||||
# ÄNDERUNG: .classes('w-full') hinzugefügt
|
||||
p2_points = ui.slider(min=0, max=100, value=10).props("label-always").classes('w-full')
|
||||
|
||||
ui.space()
|
||||
|
||||
# Das Match in die Datenbank eintragen lassen und die MMR Berechnung triggern.
|
||||
def input_match_to_database():
|
||||
# 1. Prüfen, ob ein Gegner ausgewählt wurde
|
||||
p2_id = opponent_select.value
|
||||
if p2_id is None:
|
||||
ui.notify("Bitte wähle zuerst einen Gegner aus!", color="red", position="top")
|
||||
return # Bricht die Funktion hier ab!
|
||||
return
|
||||
|
||||
# 2. Punkte von den Slidern holen
|
||||
score_p1 = p1_points.value
|
||||
score_p2 = p2_points.value
|
||||
|
||||
# 3. Daten an die API schicken (my_id haben wir oben schon von app.storage.user geholt)
|
||||
match_id = data_api.add_new_match(systemname, my_id, p2_id, score_p1, score_p2)
|
||||
|
||||
# 4. Erfolgsmeldung und zurück zur Übersicht
|
||||
# 4. Erfolgsmeldung und Berechnung
|
||||
ui.notify("Match erfolgreich eingetragen!", color="green")
|
||||
calc_match.calculate_inserted_match(systemname, match_id)
|
||||
ui.navigate.to('/')
|
||||
|
||||
with ui.row().classes("w-full items-center justify-between"):
|
||||
ui.button('Cancel', on_click=lambda: ui.navigate.to('/')).classes('bg-gray-500 text-white')
|
||||
ui.button(text="Absenden", color="positive", on_click=lambda: input_match_to_database()).classes("")
|
||||
# Buttons ganz unten in einer Reihe
|
||||
with ui.row().classes("w-full items-center justify-between mt-8"):
|
||||
ui.button('Cancel', on_click=lambda: ui.navigate.to('/statistic/{systemname}')).classes('bg-gray-500 text-white')
|
||||
ui.button(text="Absenden", color="positive", on_click=lambda: input_match_to_database())
|
||||
|
|
|
|||
73
match_calculations/calc_match.py
Normal file
73
match_calculations/calc_match.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
from data import data_api
|
||||
from match_calculations import calc_mmr_change
|
||||
import json
|
||||
import os
|
||||
|
||||
# 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_inserted_match (gamesystem, match_id):
|
||||
match_data = data_api.get_match_by_id(match_id)
|
||||
if not match_data:
|
||||
print("Fehler: Match nicht gefunden!")
|
||||
return
|
||||
# Laden und umsetzen der Match Daten
|
||||
p1_id = match_data['player1_id']
|
||||
p2_id = match_data['player2_id']
|
||||
p1_score = match_data['score_player1']
|
||||
p2_score = match_data['score_player2']
|
||||
sys_name = match_data['gamesystem_name']
|
||||
sys_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
|
||||
winner_score = 0
|
||||
looser_score = 0
|
||||
|
||||
|
||||
# Abgrenzen ob das Match schon berechnet wurde. Weil ein Draw kann 4 Punkte unterschied haben
|
||||
# 43-41 ist ein Draw aber rein Mathematisch würde es auch ein anderes if triggern
|
||||
# Abgrenzen, wer gewonnen hat (if, elif, else Kette)
|
||||
|
||||
# 1. Ist es ein Unentschieden (Draw)?
|
||||
if -draw_diff <= (p1_score - p2_score) <= draw_diff:
|
||||
match_is_draw = True
|
||||
winner_id = p1_id # Bei Draw ist egal, wer wo steht
|
||||
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:
|
||||
match_is_draw = False
|
||||
winner_id = p1_id
|
||||
looser_id = p2_id
|
||||
winner_score = p1_score
|
||||
looser_score = p2_score
|
||||
|
||||
# 3. Wenn weder Draw noch P1 Sieg, MUSS P2 gewonnen haben!
|
||||
else:
|
||||
match_is_draw = False
|
||||
winner_id = p2_id
|
||||
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)
|
||||
|
||||
|
||||
|
||||
def load_mmr_rule_matrix(systemname):
|
||||
safe_name = systemname.replace(" ", "_").lower()
|
||||
|
||||
file_path = f"match_calculations/mmr_rules_{safe_name}.json"
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
rules = json.load(file)
|
||||
|
||||
return rules
|
||||
65
match_calculations/calc_mmr_change.py
Normal file
65
match_calculations/calc_mmr_change.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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")
|
||||
113
match_calculations/mmr_rules_spearhead.json
Normal file
113
match_calculations/mmr_rules_spearhead.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"system_info": "Balancing für Spearhead",
|
||||
"draw_point_difference": 5,
|
||||
"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
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
112
match_calculations/mmr_rules_warhammer_40k.json
Normal file
112
match_calculations/mmr_rules_warhammer_40k.json
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"system_info": "Balancing für Warhammer 40k",
|
||||
"draw_point_difference": 5,
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
113
match_calculations/mmr_rules_warhammer_age_of_sigmar.json
Normal file
113
match_calculations/mmr_rules_warhammer_age_of_sigmar.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"system_info": "Balancing für Warhammer Age of Sigmar",
|
||||
"draw_point_difference": 5,
|
||||
"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,62 +0,0 @@
|
|||
from data import data_api
|
||||
from mmr_calculations import *
|
||||
|
||||
# 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_inserted_match (gamesystem, match_id):
|
||||
match_data = data_api.get_match_by_id(match_id)
|
||||
if not match_data:
|
||||
print("Fehler: Match nicht gefunden!")
|
||||
return
|
||||
# Laden und umsetzen der Match Daten
|
||||
p1_id = match_data['player1_id']
|
||||
p2_id = match_data['player2_id']
|
||||
p1_score = match_data['score_player1']
|
||||
p2_score = match_data['score_player2']
|
||||
sys_name = match_data['gamesystem_name']
|
||||
sys_id = match_data['gamesystem_id']
|
||||
|
||||
rules = load_mmr_rule_matrix(systemname)
|
||||
draw_diff = rules["draw_point_difference"]
|
||||
|
||||
calculated = False
|
||||
winner_id = None
|
||||
looser_id = None
|
||||
match_is_draw = False
|
||||
winner_score = 0
|
||||
looser_score = 0
|
||||
|
||||
# Abgrenzen ob das Match schon berechnet wurde. Weil ein Draw kan 4 Punkte unterschied haben
|
||||
# 43-41 ist ein Draw aber rein Mathematisch würde es auch ein anderes if triggern
|
||||
while not calculated:
|
||||
# Match is a Draw
|
||||
if -draw_diff <= (p1_score-p2_score) <= draw_diff:
|
||||
match_is_draw = True
|
||||
winner_id = p1_id
|
||||
looser_id = p2_id
|
||||
winner_score = p1_score
|
||||
looser_score = p2_score
|
||||
|
||||
calculated = True
|
||||
break
|
||||
|
||||
# p1 ist der Sieger.
|
||||
if score_p1 > score_p2:
|
||||
winner_id = p1_id
|
||||
looser_id = p2_id
|
||||
winner_score = p1_score
|
||||
looser_score = p2_score
|
||||
|
||||
calculated = True
|
||||
break
|
||||
|
||||
# p2 ist der Sieger.
|
||||
if score_p1 < score_p2:
|
||||
winner_id = p2_id
|
||||
looser_id = p1_id
|
||||
winner_score = p2_score
|
||||
looser_score = p1_score
|
||||
|
||||
calculated = True
|
||||
break
|
||||
|
||||
calc_mmr_change.calc_mmr_change(sys_name, winner_id, looser_id, winner_score, looser_score, match_is_draw)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from data import data_api
|
||||
import json
|
||||
import os
|
||||
|
||||
def calc_mmr_change(systemname, winner_id, looser_id, winner_points, looser_points, match_is_draw):
|
||||
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)
|
||||
|
||||
rules = load_mmr_rule_matrix(systemname)
|
||||
|
||||
if match_is_draw:
|
||||
mmr_change = rules["rank_matrix"][str(winner_rank-looser_rank)]["draw"]
|
||||
|
||||
else:
|
||||
mmr_change = rules["rank_matrix"][str(winner_rank-looser_rank)]["win"]
|
||||
|
||||
|
||||
|
||||
|
||||
def load_mmr_rule_matrix(systemname):
|
||||
safe_name = systemname.replace(" ", "_").lower()
|
||||
|
||||
file_path = f"mmr_calculations/mmr_rules_{safe_name}.json"
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
rules = json.load(file)
|
||||
|
||||
return rules
|
||||
|
||||
Loading…
Reference in New Issue
Block a user