From 5b9a17d3a33d54b6bce504bb877257bc3c5efe25 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 9 Apr 2022 12:04:24 +0300 Subject: [PATCH] Correctly advance byes when many Closes #803 --- app/core/tournament/algorithms.ts | 9 ++++- app/core/tournament/bracket.test.ts | 54 ++++++++++++++++++++++++++--- app/core/tournament/bracket.ts | 51 ++++++++++++++++++--------- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/app/core/tournament/algorithms.ts b/app/core/tournament/algorithms.ts index 7a95f88a9..afaed08eb 100644 --- a/app/core/tournament/algorithms.ts +++ b/app/core/tournament/algorithms.ts @@ -18,6 +18,7 @@ export function eliminationBracket( const matchesLQueue: Match[] = []; const backfillQ: Match[] = []; let matchNumber = 1; + let matchPosition = 1; invariant( powerOf2(participants.length), @@ -177,13 +178,16 @@ export function eliminationBracket( return bracket; function createMatch( - args: Omit, + args: Omit, willBeSkipped?: boolean ): Match { const number = willBeSkipped ? 0 : matchNumber++; + const position = matchPosition++; + return { id: uuidv4(), number, + position, ...args, }; } @@ -238,7 +242,10 @@ export type TeamIdentifier = number | "BYE"; export interface Match { id: string; + /** Match number as displayed on bracket. 0 if match should not show. */ number: number; + /** Match position that decides the order in which matches are displayed. No zeros. */ + position: number; upperTeam?: TeamIdentifier; lowerTeam?: TeamIdentifier; winner?: TeamIdentifier; diff --git a/app/core/tournament/bracket.test.ts b/app/core/tournament/bracket.test.ts index 6105fbc7e..dd1289af9 100644 --- a/app/core/tournament/bracket.test.ts +++ b/app/core/tournament/bracket.test.ts @@ -60,13 +60,24 @@ RoundNames("No bracket reset round for SE", () => { assert.not.ok(hasBR); }); -const mapPool = mapPoolForTest(); -const bracket = eliminationBracket(24, "DE"); -const rounds = getRoundsDefaultBestOf(bracket); -const mapList = generateMapListForRounds({ mapPool, rounds }); +const testTournamentData = (type: "SE" | "DE", participantsCount: number) => { + const mapPool = mapPoolForTest(); + const bracket = eliminationBracket(participantsCount, type); + const rounds = getRoundsDefaultBestOf(bracket); + const mapList = generateMapListForRounds({ mapPool, rounds }); + + return { + mapPool, + bracket, + rounds, + mapList, + }; +}; TournamentRoundsForDB("Generates rounds correctly", () => { const TEAM_COUNT = 24; + + const { bracket, mapList } = testTournamentData("DE", TEAM_COUNT); const bracketForDb = tournamentRoundsForDB({ mapList, bracketType: "DE", @@ -104,7 +115,42 @@ TournamentRoundsForDB("Generates rounds correctly", () => { assert.equal(uniqueParticipants.size, TEAM_COUNT + 1); // + BYE }); +TournamentRoundsForDB( + "Generates rounds correctly (many byes, correct amount of teams round 2)", + () => { + const TEAM_COUNT = 18; + + const { mapList } = testTournamentData("SE", TEAM_COUNT); + + const bracketForDb = tournamentRoundsForDB({ + mapList, + bracketType: "SE", + participantsSeeded: new Array(TEAM_COUNT) + .fill(null) + .map((_, i) => i + 1) + .map(String) + .map((id) => ({ id })), + }); + + const participantsInRoundTwo = bracketForDb[1].matches.reduce( + (acc, cur) => { + const participants = cur.participants.reduce( + (acc, cur) => acc + (cur.team === "BYE" ? 0 : 1), + 0 + ); + + return acc + participants; + }, + 0 + ); + + assert.equal(participantsInRoundTwo, 14); + } +); + TournamentRoundsForDB("Advances bye to right spot", () => { + const { mapList } = testTournamentData("DE", 24); + const TEAM_COUNT = 7; const bracketForDb = tournamentRoundsForDB({ mapList, diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts index 9ba4a99cf..7096d05e1 100644 --- a/app/core/tournament/bracket.ts +++ b/app/core/tournament/bracket.ts @@ -363,39 +363,53 @@ function advanceByes( const teamsForSecondRound = new Map< number, - ["upperTeam" | "lowerTeam", number] + ["upperTeam" | "lowerTeam", number][] >(); for (const round of result.winners[0]) { const winnerDestinationMatch = round.winnerDestinationMatch; invariant(winnerDestinationMatch, "winnerDestinationmatch is undefined"); + const teamsForSecondRoundArr = + teamsForSecondRound.get(winnerDestinationMatch.number) ?? []; + let changed = false; if ( round.upperTeam && round.upperTeam !== "BYE" && round.lowerTeam === "BYE" ) { - teamsForSecondRound.set(winnerDestinationMatch.number, [ + teamsForSecondRoundArr.push([ resolveSide(round, winnerDestinationMatch, result), round.upperTeam, ]); + changed = true; } else if ( round.lowerTeam && round.lowerTeam !== "BYE" && round.upperTeam === "BYE" ) { - teamsForSecondRound.set(winnerDestinationMatch.number, [ + teamsForSecondRoundArr.push([ resolveSide(round, winnerDestinationMatch, result), round.lowerTeam, ]); + changed = true; + } + + if (changed) { + teamsForSecondRound.set( + winnerDestinationMatch.number, + teamsForSecondRoundArr + ); } } for (const [i, round] of result.winners[1].entries()) { - const teamForSecondRound = teamsForSecondRound.get(round.number); - if (!teamForSecondRound) continue; + const teamForSecondRoundArr = teamsForSecondRound.get(round.number); + if (!teamForSecondRoundArr) continue; - const [key, teamNumber] = teamForSecondRound; - result.winners[1][i] = { ...result.winners[1][i], [key]: teamNumber }; + for (const teamForSecondRound of teamForSecondRoundArr) { + const [key, teamNumber] = teamForSecondRound; + result.winners[1][i] = { ...result.winners[1][i], [key]: teamNumber }; + } } return result; @@ -406,22 +420,25 @@ function resolveSide( destinationMatch: Match, rounds: EliminationBracket ): "upperTeam" | "lowerTeam" { - const matchNumbers = getWinnerDestinationMatchIdToMatchNumbers(rounds).get( - destinationMatch.id + const matchPositions = getWinnerDestinationMatchIdToMatchPositions( + rounds + ).get(destinationMatch.id); + const otherPosition = matchPositions?.find( + (num) => num !== currentMatch.position ); - const otherNumber = matchNumbers?.find((num) => num !== currentMatch.number); + invariant( - otherNumber, - `no otherNumber; matchNumbers length is not 2 was: ${ - matchNumbers?.length ?? "NO_LENGTH" + otherPosition, + `no otherPosition; matchPositions length was: ${ + matchPositions?.length ?? "NO_LENGTH" }` ); - if (otherNumber > currentMatch.number) return "upperTeam"; + if (otherPosition > currentMatch.position) return "upperTeam"; return "lowerTeam"; } -function getWinnerDestinationMatchIdToMatchNumbers( +function getWinnerDestinationMatchIdToMatchPositions( rounds: EliminationBracket ): Map { return rounds.winners[0].reduce((map, round) => { @@ -430,12 +447,12 @@ function getWinnerDestinationMatchIdToMatchNumbers( "round.winnerDestinationMatch is undefined" ); if (!map.has(round.winnerDestinationMatch.id)) { - return map.set(round.winnerDestinationMatch.id, [round.number]); + return map.set(round.winnerDestinationMatch.id, [round.position]); } const arr = map.get(round.winnerDestinationMatch.id); invariant(arr, "arr is undefined"); - arr.push(round.number); + arr.push(round.position); return map; }, new Map());