Add create, join and end endpoints for 2v2 matches.
Data will be calculated using OpenSkill (see previous commit). all players will start with an openskill median of 25 and a 8.3 uncertainty value.
This commit is contained in:
parent
3068a21bc6
commit
2b01c0078e
172
main.py
172
main.py
|
@ -4,7 +4,8 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||||
from calls import *
|
from calls import *
|
||||||
from db import get_db_connection
|
from db import get_db_connection
|
||||||
import requests
|
import requests
|
||||||
|
from openskill.models import PlackettLuce
|
||||||
|
model = PlackettLuce()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
@ -20,6 +21,11 @@ class RegisterRequest(BaseModel):
|
||||||
display_name: str
|
display_name: str
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
class JoinMatch2v2Request(BaseModel):
|
||||||
|
token: str
|
||||||
|
match_id: int
|
||||||
|
slot: str
|
||||||
|
|
||||||
class LoginRequest(BaseModel):
|
class LoginRequest(BaseModel):
|
||||||
email: str
|
email: str
|
||||||
password: str
|
password: str
|
||||||
|
@ -51,6 +57,12 @@ class EndMatchRequest(BaseModel):
|
||||||
player1_score: int
|
player1_score: int
|
||||||
player2_score: int
|
player2_score: int
|
||||||
|
|
||||||
|
class EndFourMatch(BaseModel):
|
||||||
|
match_id: int
|
||||||
|
player1_team1_score: int
|
||||||
|
player2_team1_score: int
|
||||||
|
player1_team2_score: int
|
||||||
|
player2_team2_score: int
|
||||||
|
|
||||||
@app.post("/register")
|
@app.post("/register")
|
||||||
def register(request: RegisterRequest):
|
def register(request: RegisterRequest):
|
||||||
|
@ -459,40 +471,107 @@ def get_latest_commit_hashes():
|
||||||
import trueskill
|
import trueskill
|
||||||
ts = trueskill.TrueSkill(mu=25.0, sigma=8.333, beta=4.166, tau=0.083, draw_probability=0.1)
|
ts = trueskill.TrueSkill(mu=25.0, sigma=8.333, beta=4.166, tau=0.083, draw_probability=0.1)
|
||||||
|
|
||||||
class EndFourMatch(BaseModel):
|
|
||||||
match_id: int
|
|
||||||
player1_team1_score: int
|
|
||||||
player2_team1_score: int
|
|
||||||
player1_team2_score: int
|
|
||||||
player2_team2_score: int
|
|
||||||
|
|
||||||
def get_rolling_base():
|
@app.post("/creatematch_2v2")
|
||||||
conn = get_db_connection()
|
def create_match_2v2(request: CreateMatchRequest):
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute(
|
|
||||||
"""
|
|
||||||
SELECT AVG(current_elo) AS rolling_base
|
|
||||||
FROM users
|
|
||||||
WHERE current_elo != 0;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
result = cursor.fetchone()
|
|
||||||
return result["rolling_base"] if result else 10
|
|
||||||
|
|
||||||
def elo_to_trueskill(elo, base, k=40):
|
|
||||||
mu = (elo - base) / k
|
|
||||||
sigma = 8.333
|
|
||||||
return ts.create_rating(mu=mu, sigma=sigma)
|
|
||||||
|
|
||||||
@app.post("/endfour")
|
|
||||||
async def end_four_match(request: EndFourMatch):
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
try:
|
try:
|
||||||
|
player1_uid = get_uid_by_token(request.token)
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO matches_2v2 (player1_team1_uid)
|
||||||
|
VALUES (%s)
|
||||||
|
RETURNING match_id;
|
||||||
|
""",
|
||||||
|
(player1_uid,)
|
||||||
|
)
|
||||||
|
match_id = cursor.fetchone()["match_id"]
|
||||||
|
conn.commit()
|
||||||
|
return {"match_id": match_id}
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
class JoinMatch2v2Request(BaseModel):
|
||||||
|
token: str
|
||||||
|
match_id: int
|
||||||
|
slot: str # e.g., "player1_team1", "player2_team1", "player1_team2", "player2_team2"
|
||||||
|
|
||||||
|
@app.post("/joinmatch_2v2")
|
||||||
|
def join_match_2v2(request: JoinMatch2v2Request):
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
player_uid = get_uid_by_token(request.token)
|
||||||
|
|
||||||
|
# Map slot names to database column names
|
||||||
|
valid_slots = {
|
||||||
|
"player2_team1": "player2_team1_uid",
|
||||||
|
"player1_team2": "player1_team2_uid",
|
||||||
|
"player2_team2": "player2_team2_uid",
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.slot not in valid_slots:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Invalid slot: {request.slot}")
|
||||||
|
|
||||||
|
column_name = valid_slots[request.slot]
|
||||||
|
|
||||||
|
# Check if the match exists and the slot is available
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT {column_name}
|
||||||
|
FROM matches_2v2
|
||||||
|
WHERE match_id = %s;
|
||||||
|
""",
|
||||||
|
(request.match_id,)
|
||||||
|
)
|
||||||
|
slot_status = cursor.fetchone()
|
||||||
|
if not slot_status:
|
||||||
|
raise HTTPException(status_code=404, detail="Match not found")
|
||||||
|
|
||||||
|
if slot_status[column_name] is not None:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Slot '{request.slot}' is already occupied")
|
||||||
|
|
||||||
|
# Update the match to assign the player to the specified slot
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE matches_2v2
|
||||||
|
SET {column_name} = %s
|
||||||
|
WHERE match_id = %s;
|
||||||
|
""",
|
||||||
|
(player_uid, request.match_id)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"Player successfully joined as {request.slot}",
|
||||||
|
"match_id": request.match_id,
|
||||||
|
"slot": request.slot,
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/endfour_openskill")
|
||||||
|
async def end_four_match_openskill(request: EndFourMatch):
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
# Fetch player IDs and their current ratings
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
SELECT player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid
|
SELECT player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid
|
||||||
FROM fourmatches
|
FROM matches_2v2
|
||||||
WHERE match_id = %s;
|
WHERE match_id = %s;
|
||||||
""",
|
""",
|
||||||
(request.match_id,)
|
(request.match_id,)
|
||||||
|
@ -505,35 +584,50 @@ async def end_four_match(request: EndFourMatch):
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
SELECT uid, trueskill_mu, trueskill_sigma
|
SELECT uid, trueskill_mu AS mu, trueskill_sigma AS sigma
|
||||||
FROM users
|
FROM users
|
||||||
WHERE uid IN (%s, %s, %s, %s);
|
WHERE uid IN (%s, %s, %s, %s);
|
||||||
""",
|
""",
|
||||||
(player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid)
|
(player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid)
|
||||||
)
|
)
|
||||||
players = {
|
players = {
|
||||||
row["uid"]: ts.create_rating(mu=row["trueskill_mu"], sigma=row["trueskill_sigma"])
|
row["uid"]: model.rating(mu=row["mu"], sigma=row["sigma"])
|
||||||
for row in cursor.fetchall()
|
for row in cursor.fetchall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Default ratings for any missing players
|
||||||
|
default_rating = model.rating(mu=25, sigma=8.333)
|
||||||
|
players = {
|
||||||
|
player1_team1_uid: players.get(player1_team1_uid, default_rating),
|
||||||
|
player2_team1_uid: players.get(player2_team1_uid, default_rating),
|
||||||
|
player1_team2_uid: players.get(player1_team2_uid, default_rating),
|
||||||
|
player2_team2_uid: players.get(player2_team2_uid, default_rating),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate team scores
|
||||||
team1_score = request.player1_team1_score + request.player2_team1_score
|
team1_score = request.player1_team1_score + request.player2_team1_score
|
||||||
team2_score = request.player1_team2_score + request.player2_team2_score
|
team2_score = request.player1_team2_score + request.player2_team2_score
|
||||||
|
|
||||||
|
# Assign ranks based on scores
|
||||||
ranks = [0, 1] if team1_score > team2_score else [1, 0]
|
ranks = [0, 1] if team1_score > team2_score else [1, 0]
|
||||||
|
|
||||||
new_ratings = ts.rate(
|
# Update ratings using OpenSkill
|
||||||
|
updated_ratings = model.rate(
|
||||||
[[players[player1_team1_uid], players[player2_team1_uid]],
|
[[players[player1_team1_uid], players[player2_team1_uid]],
|
||||||
[players[player1_team2_uid], players[player2_team2_uid]]],
|
[players[player1_team2_uid], players[player2_team2_uid]]],
|
||||||
ranks=ranks
|
ranks=ranks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Flatten the results for database updates
|
||||||
updates = [
|
updates = [
|
||||||
(uid, rating.mu, rating.sigma)
|
(uid, rating.mu, rating.sigma)
|
||||||
for uid, rating in zip(
|
for uid, rating in zip(
|
||||||
[player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid],
|
[player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid],
|
||||||
[new_ratings[0][0], new_ratings[0][1], new_ratings[1][0], new_ratings[1][1]]
|
[updated_ratings[0][0], updated_ratings[0][1], updated_ratings[1][0], updated_ratings[1][1]]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Update user ratings in the database
|
||||||
for uid, new_mu, new_sigma in updates:
|
for uid, new_mu, new_sigma in updates:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
|
@ -547,18 +641,23 @@ async def end_four_match(request: EndFourMatch):
|
||||||
# Update match results
|
# Update match results
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE fourmatches
|
UPDATE matches_2v2
|
||||||
SET player1_team1_score = %s, player2_team1_score = %s,
|
SET player1_team1_score = %s, player2_team1_score = %s,
|
||||||
player1_team2_score = %s, player2_team2_score = %s
|
player1_team2_score = %s, player2_team2_score = %s,
|
||||||
|
team1_score = %s, team2_score = %s,
|
||||||
|
winner_team = %s
|
||||||
WHERE match_id = %s;
|
WHERE match_id = %s;
|
||||||
""",
|
""",
|
||||||
(request.player1_team1_score, request.player2_team1_score,
|
(request.player1_team1_score, request.player2_team1_score,
|
||||||
request.player1_team2_score, request.player2_team2_score, request.match_id)
|
request.player1_team2_score, request.player2_team2_score,
|
||||||
|
team1_score, team2_score,
|
||||||
|
1 if team1_score > team2_score else 2, # Determine the winner team
|
||||||
|
request.match_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return {"message": "Match ended and TrueSkill ratings updated successfully"}
|
return {"message": "Match ended and OpenSkill ratings updated successfully"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
|
@ -567,4 +666,5 @@ async def end_four_match(request: EndFourMatch):
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
Loading…
Reference in a new issue