Correctly advance byes when many Closes #803

This commit is contained in:
Kalle
2022-04-09 12:04:24 +03:00
parent da08301b54
commit 5b9a17d3a3
3 changed files with 92 additions and 22 deletions

View File

@@ -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<Match, "id" | "number">,
args: Omit<Match, "id" | "number" | "position">,
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;

View File

@@ -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,

View File

@@ -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<Match[][]>
): "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<Match[][]>
): Map<string, number[]> {
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<string, number[]>());