import type { Tables } from "~/db/tables"; import type { FindByTournamentTeamIdItem } from "~/features/tournament-match/TournamentMatchRepository.server"; import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; import { sourceTypes } from "~/modules/tournament-map-list-generator/constants"; import type { TournamentMaplistSource } from "~/modules/tournament-map-list-generator/types"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; export interface AllRoundsItem { stageId: number; stageName: string; stageType: Tables["TournamentStage"]["type"]; roundNumber: number; groupNumber: number; } export interface PlayedSet { tournamentMatchId: number; score: [teamBeingViewed: number, opponent: number]; round: { type: "winners" | "losers" | "single_elim" | "round_robin" | "swiss"; round: number | "finals" | "grand_finals" | "bracket_reset"; }; stageName: string; maps: Array<{ stageId: StageId; modeShort: ModeShort; result: "win" | "loss"; source: TournamentMaplistSource; }>; opponent: { id: number; name: string; /** Team's roster that played in this set */ roster: Array< Pick< Tables["User"], "id" | "username" | "discordAvatar" | "discordId" | "customUrl" > & { customAvatarUrl: string | null } >; }; } export function winCounts(sets: PlayedSet[]) { let setsWon = 0; let totalSets = 0; let mapsWon = 0; let totalMaps = 0; for (const set of sets) { let mapsWonThisSet = 0; let totalMapsThisSet = 0; for (const map of set.maps) { if (map.result === "win") { mapsWonThisSet++; } totalMapsThisSet++; } totalSets++; if (mapsWonThisSet > totalMapsThisSet / 2) { setsWon++; } mapsWon += mapsWonThisSet; totalMaps += totalMapsThisSet; } return { sets: { won: setsWon, total: totalSets, percentage: totalSets === 0 ? 0 : Math.round((setsWon / totalSets) * 100), }, maps: { won: mapsWon, total: totalMaps, percentage: totalMaps === 0 ? 0 : Math.round((mapsWon / totalMaps) * 100), }, }; } export function tournamentTeamSets({ sets, allRounds, }: { sets: FindByTournamentTeamIdItem[]; allRounds: AllRoundsItem[]; }): PlayedSet[] { return sets.map((set) => { const round = allRounds.find((round) => round.stageId === set.stageId) ?? allRounds[0]; const resolveRound = () => { if (round.stageType === "round_robin" || round.stageType === "swiss") { return set.roundNumber; } if (set.groupNumber === 3) { if (set.roundNumber === 2) return "bracket_reset"; return "grand_finals"; } const maxRoundNumberOfGroup = Math.max( ...allRounds .filter( (round) => round.groupNumber === set.groupNumber && round.stageId === set.stageId, ) .map((round) => round.roundNumber), ); if (set.roundNumber === maxRoundNumberOfGroup) { return "finals"; } return set.roundNumber; }; return { tournamentMatchId: set.tournamentMatchId, stageName: round.stageName, round: { round: resolveRound(), type: resolveRoundType({ groupNumber: set.groupNumber, stageType: round.stageType, }), }, maps: set.matches.map((match) => ({ stageId: match.stageId, modeShort: match.mode, result: match.wasWinner ? "win" : "loss", source: parseTournamentMaplistSource(match.source), })), score: flipScoreIfNeeded(set), opponent: { id: set.otherTeamId, name: set.otherTeamName, roster: set.players, }, }; }); } function parseTournamentMaplistSource(source: string): TournamentMaplistSource { if (sourceTypes.includes(source as any)) { return source as TournamentMaplistSource; } const parsed = Number(source); invariant(!Number.isNaN(parsed), `Invalid source: ${source}`); return parsed; } function flipScoreIfNeeded(set: FindByTournamentTeamIdItem): [number, number] { const score: [number, number] = [ set.opponentOneScore ?? 0, set.opponentTwoScore ?? 0, ]; const wonTheSet = set.matches.reduce((acc, cur) => cur.wasWinner + acc, 0) > set.matches.length / 2; if ( (wonTheSet && score[0] < score[1]) || (!wonTheSet && score[0] > score[1]) ) { return [score[1], score[0]]; } return score; } function resolveRoundType({ groupNumber, stageType, }: { groupNumber: number; stageType: Tables["TournamentStage"]["type"]; }) { if (stageType === "single_elimination") { return "single_elim"; } if (stageType === "round_robin") { return "round_robin"; } if (stageType === "swiss") { return "swiss"; } if (groupNumber === 1 || groupNumber === 3) { return "winners"; } if (groupNumber === 2) { return "losers"; } logger.warn( `resolveRoundType: groupNumber ${groupNumber} and stageType ${stageType} not handled`, ); return "single_elim"; }