diff --git a/app/core/tournament/algorithms.test.ts b/app/core/tournament/algorithms.test.ts index 4590c0aeb..b6e8e5b33 100644 --- a/app/core/tournament/algorithms.test.ts +++ b/app/core/tournament/algorithms.test.ts @@ -6,10 +6,12 @@ import { Match, TeamIdentifier, } from "./algorithms"; +import { countRounds } from "./bracket"; const AmountOfTeams = suite("Amount of teams"); const Byes = suite("Byes"); const Seeds = suite("Seeds"); +const BracketPaths = suite("Bracket path"); const FillParticipantsWithNull = suite( "fillParticipantsWithNullTillPowerOfTwo()" ); @@ -81,6 +83,52 @@ Seeds("First and second seed are spread apart", () => { ); }); +BracketPaths("Following winners", () => { + const bracket16 = eliminationBracket(16, "DE"); + const count = countRounds(bracket16); + + let latest: Match = bracket16.winners[0]; + let rounds = 0; + let roundIds = new Set(); + while (latest) { + rounds++; + roundIds.add(latest.id); + if (!latest.winnerDestinationMatch) { + break; + } + assert.equal(latest.side, "winners"); + assert.ok(latest.loserDestinationMatch); + latest = latest.winnerDestinationMatch; + } + + // no duplicate rounds + assert.equal(roundIds.size, rounds); + + assert.equal(rounds, count.winners); +}); + +BracketPaths("Following losers", () => { + const bracket16 = eliminationBracket(16, "DE"); + const count = countRounds(bracket16); + + let latest: Match = bracket16.winners[0]; + let rounds = 0; + let roundIds = new Set(); + while (latest) { + rounds++; + roundIds.add(latest.id); + if (!latest.winnerDestinationMatch && !latest.loserDestinationMatch) { + break; + } + latest = (latest.loserDestinationMatch ?? latest.winnerDestinationMatch)!; + } + + // no duplicate rounds + assert.equal(roundIds.size, rounds); + + assert.equal(rounds, count.losers + 3); // 3 rounds of winners: first round, grand finals & bracket reset +}); + FillParticipantsWithNull("17", () => { const participants: TeamIdentifier[] = new Array(17) .fill(null) @@ -120,4 +168,5 @@ function teamWithBye(matches: Match[]) { AmountOfTeams.run(); Byes.run(); Seeds.run(); +BracketPaths.run(); FillParticipantsWithNull.run(); diff --git a/app/core/tournament/algorithms.ts b/app/core/tournament/algorithms.ts index fdda0698d..c5ef9f356 100644 --- a/app/core/tournament/algorithms.ts +++ b/app/core/tournament/algorithms.ts @@ -1,5 +1,6 @@ import invariant from "tiny-invariant"; import { v4 as uuidv4 } from "uuid"; +import type { EliminationBracketSide } from "./bracket"; /** Singe/Double Elimination bracket algorithm that handles byes * @link https://stackoverflow.com/a/59615574 */ @@ -57,6 +58,7 @@ export function eliminationBracket( const firstRoundMatch = createMatch({ upperTeam, lowerTeam, + side: "winners", }); matchesWQueue.push(firstRoundMatch); @@ -74,8 +76,12 @@ export function eliminationBracket( const winnersBracketMatch = createMatch({ match1, match2, + side: "winners", }); + match1.winnerDestinationMatch = winnersBracketMatch; + match2.winnerDestinationMatch = winnersBracketMatch; + matchesWQueue.push(winnersBracketMatch); bracket.winners.push(winnersBracketMatch); // add match to backfill for Lower Queue @@ -121,8 +127,20 @@ export function eliminationBracket( const losersMatch = createMatch({ match1, match2, + side: "losers", }); + match1[ + match1.side === "winners" + ? "loserDestinationMatch" + : "winnerDestinationMatch" + ] = losersMatch; + match2[ + match2.side === "winners" + ? "loserDestinationMatch" + : "winnerDestinationMatch" + ] = losersMatch; + matchesLQueue.push(losersMatch); bracket.losers.push(losersMatch); } @@ -137,13 +155,23 @@ export function eliminationBracket( const grandFinals = createMatch({ match1, match2, + side: "winners", }); + + match1.winnerDestinationMatch = grandFinals; + match2.winnerDestinationMatch = grandFinals; + bracket.winners.push(grandFinals); const bracketReset = createMatch({ match1: grandFinals, match2: grandFinals, + side: "winners", }); + + grandFinals.winnerDestinationMatch = bracketReset; + grandFinals.loserDestinationMatch = bracketReset; + bracket.winners.push(bracketReset); return bracket; @@ -212,6 +240,9 @@ export interface Match { match1?: Match; /** Match that leads to this match */ match2?: Match; + loserDestinationMatch?: Match; + winnerDestinationMatch?: Match; + side: EliminationBracketSide; } export interface Bracket {