diff --git a/src/app/api/matches/route.ts b/src/app/api/matches/route.ts index dbd5c90..5105863 100644 --- a/src/app/api/matches/route.ts +++ b/src/app/api/matches/route.ts @@ -3,6 +3,8 @@ import { prisma } from "@/lib/prisma"; import { getSession } from "@/lib/auth-simple"; import { calculateTeamElo, calculateTeamEloChange } from "@/lib/elo-utils"; +import { calculateOpenSkillRatings, fromOpenSkillRating } from "@/lib/openskill-utils"; +import { calculateGlicko2Ratings } from "@/lib/glicko2-utils"; const K_FACTOR = 32; // Standard K-factor for Elo calculations @@ -80,10 +82,7 @@ export async function POST(request: Request) { ); } - // Calculate Elo changes - const team1Rating = calculateTeamElo(player1!.currentElo, player2!.currentElo); - const team2Rating = calculateTeamElo(player3!.currentElo, player4!.currentElo); - + // Calculate match outcome const team1Won = team1Score > team2Score; const team2Won = team2Score > team1Score; const isTie = team1Score === team2Score; @@ -91,6 +90,10 @@ export async function POST(request: Request) { const team1ScoreActual = isTie ? 0.5 : (team1Won ? 1 : 0); const team2ScoreActual = isTie ? 0.5 : (team2Won ? 1 : 0); + // Calculate Elo changes + const team1Rating = calculateTeamElo(player1!.currentElo, player2!.currentElo); + const team2Rating = calculateTeamElo(player3!.currentElo, player4!.currentElo); + const team1EloChange = calculateTeamEloChange(team1Rating, team2Rating, team1ScoreActual, K_FACTOR); const team2EloChange = calculateTeamEloChange(team2Rating, team1Rating, team2ScoreActual, K_FACTOR); @@ -99,6 +102,35 @@ export async function POST(request: Request) { const player3EloChange = team2EloChange / 2; const player4EloChange = team2EloChange / 2; + // Calculate OpenSkill ratings + const player1OpenSkill = { mu: player1!.currentElo, sigma: 8.33 }; + const player2OpenSkill = { mu: player2!.currentElo, sigma: 8.33 }; + const player3OpenSkill = { mu: player3!.currentElo, sigma: 8.33 }; + const player4OpenSkill = { mu: player4!.currentElo, sigma: 8.33 }; + + const rankings = isTie ? [0, 0] : team1Won ? [0, 1] : [1, 0]; + const openSkillTeams = [[player1OpenSkill, player2OpenSkill], [player3OpenSkill, player4OpenSkill]]; + const newOpenSkillRatings = calculateOpenSkillRatings(openSkillTeams, rankings); + + // Calculate Glicko2 ratings + const Glicko2 = require('glicko2').Glicko2; + const glicko = new Glicko2(); + const glicko1 = glicko.makePlayer(player1!.currentElo, 350, 0.06); + const glicko2 = glicko.makePlayer(player2!.currentElo, 350, 0.06); + const glicko3 = glicko.makePlayer(player3!.currentElo, 350, 0.06); + const glicko4 = glicko.makePlayer(player4!.currentElo, 350, 0.06); + + const glickoMatches: [any, any, number][] = []; + const outcome = isTie ? 0.5 : team1Won ? 1 : 0; + + [glicko1, glicko2].forEach(p1 => { + [glicko3, glicko4].forEach(p2 => { + glickoMatches.push([p1, p2, outcome]); + }); + }); + + glicko.updateRatings(glickoMatches); + // Create match const match = await prisma.match.create({ data: { @@ -116,12 +148,24 @@ export async function POST(request: Request) { }, }); - // Update player stats + // Update player stats (Elo) await updatePlayerStats(parseInt(team1P1Id), team1Won, player1EloChange, isTie); await updatePlayerStats(parseInt(team1P2Id), team1Won, player2EloChange, isTie); await updatePlayerStats(parseInt(team2P1Id), team2Won, player3EloChange, isTie); await updatePlayerStats(parseInt(team2P2Id), team2Won, player4EloChange, isTie); + // Update OpenSkill ratings + await updateOpenSkillRating(parseInt(team1P1Id), fromOpenSkillRating(newOpenSkillRatings[0][0]), team1Won, isTie); + await updateOpenSkillRating(parseInt(team1P2Id), fromOpenSkillRating(newOpenSkillRatings[0][1]), team1Won, isTie); + await updateOpenSkillRating(parseInt(team2P1Id), fromOpenSkillRating(newOpenSkillRatings[1][0]), team2Won, isTie); + await updateOpenSkillRating(parseInt(team2P2Id), fromOpenSkillRating(newOpenSkillRatings[1][1]), team2Won, isTie); + + // Update Glicko2 ratings + await updateGlicko2Rating(parseInt(team1P1Id), glicko1.getRating(), glicko1.getRd(), glicko1.getVol(), team1Won, isTie); + await updateGlicko2Rating(parseInt(team1P2Id), glicko2.getRating(), glicko2.getRd(), glicko2.getVol(), team1Won, isTie); + await updateGlicko2Rating(parseInt(team2P1Id), glicko3.getRating(), glicko3.getRd(), glicko3.getVol(), team2Won, isTie); + await updateGlicko2Rating(parseInt(team2P2Id), glicko4.getRating(), glicko4.getRd(), glicko4.getVol(), team2Won, isTie); + // Update partnership stats await updatePartnershipStats(parseInt(team1P1Id), parseInt(team1P2Id), team1Won, player1EloChange, isTie); await updatePartnershipStats(parseInt(team2P1Id), parseInt(team2P2Id), team2Won, player3EloChange, isTie); @@ -203,3 +247,53 @@ async function updatePartnershipStats(player1Id: number, player2Id: number, won: }); } } + +async function updateOpenSkillRating(playerId: number, rating: number, won: boolean, isTie: boolean = false) { + await prisma.openSkillRating.upsert({ + where: { playerId }, + update: { + rating, + gamesPlayed: { increment: 1 }, + wins: won ? { increment: 1 } : undefined, + losses: !won && !isTie ? { increment: 1 } : undefined, + draws: isTie ? { increment: 1 } : undefined, + lastUpdated: new Date(), + }, + create: { + playerId, + rating, + gamesPlayed: 1, + wins: won ? 1 : 0, + losses: !won && !isTie ? 1 : 0, + draws: isTie ? 1 : 0, + lastUpdated: new Date(), + }, + }); +} + +async function updateGlicko2Rating(playerId: number, rating: number, deviation: number, volatility: number, won: boolean, isTie: boolean = false) { + await prisma.glicko2Rating.upsert({ + where: { playerId }, + update: { + rating, + deviation, + volatility, + gamesPlayed: { increment: 1 }, + wins: won ? { increment: 1 } : undefined, + losses: !won && !isTie ? { increment: 1 } : undefined, + draws: isTie ? { increment: 1 } : undefined, + lastUpdated: new Date(), + }, + create: { + playerId, + rating, + deviation, + volatility, + gamesPlayed: 1, + wins: won ? 1 : 0, + losses: !won && !isTie ? 1 : 0, + draws: isTie ? 1 : 0, + lastUpdated: new Date(), + }, + }); +}