import { eloWinProbabilityWithParity } from "~/services/probability-engine"; export type SoccerMatchResult = "win" | "draw" | "loss"; export interface EloSoccerMatchOptions { baseDrawRate: number; drawDecay: number; parityFactor?: number; } export function soccerDrawProbability( eloA: number, eloB: number, baseDrawRate: number, drawDecay: number ): number { return baseDrawRate * Math.exp(-drawDecay * Math.abs(eloA - eloB)); } /** * Simulate a soccer-style Elo match with an explicit draw probability. * Returns the result from team A's perspective. */ export function simulateEloSoccerMatch( eloA: number, eloB: number, { baseDrawRate, drawDecay, parityFactor = 400 }: EloSoccerMatchOptions ): SoccerMatchResult { const pDraw = soccerDrawProbability(eloA, eloB, baseDrawRate, drawDecay); const pWinNoDraw = eloWinProbabilityWithParity(eloA, eloB, parityFactor); const pWin = (1 - pDraw) * pWinNoDraw; const rand = Math.random(); if (rand < pWin) return "win"; if (rand < pWin + pDraw) return "draw"; return "loss"; } /** Generate simple low-scoring goals for table tiebreakers after a result is known. */ export function simulateSimpleSoccerGoals(result: SoccerMatchResult): { gf: number; ga: number } { if (result === "draw") { const goals = Math.random() < 0.55 ? 1 : Math.random() < 0.75 ? 0 : 2; return { gf: goals, ga: goals }; } const winnerGoals = Math.random() < 0.55 ? 2 : Math.random() < 0.75 ? 1 : 3; const loserGoals = winnerGoals === 1 ? 0 : Math.random() < 0.35 ? 1 : 0; if (result === "win") return { gf: winnerGoals, ga: loserGoals }; return { gf: loserGoals, ga: winnerGoals }; }