mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-25 20:50:14 -05:00
Correctly advance byes when many Closes #803
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[]>());
|
||||
|
||||
Reference in New Issue
Block a user