mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-09 23:25:30 -05:00
Fix team page crash
This commit is contained in:
parent
04460d56d1
commit
b73aac0d53
|
|
@ -820,6 +820,115 @@ describe("single elimination standings - third place match", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("single elimination standings - byes in later rounds", () => {
|
||||
// Brackets created before the current engine paired the padded seeding
|
||||
// naturally, so the byes ended up next to each other and could fill both
|
||||
// sides of a first round match. The current engine spreads byes with
|
||||
// `space_between`, which makes this impossible to create today, but such
|
||||
// brackets are still stored (tournament 1252's playoffs is one). A first
|
||||
// round match that is a bye on both sides leaves the second round match it
|
||||
// feeds with a single opponent, so that match is won against a bye. The
|
||||
// semifinal won that way produces no loser, leaving only one team for the
|
||||
// third place match, which can therefore never be played.
|
||||
const legacyByeBracketData = (): BracketData => {
|
||||
const stageId = 0;
|
||||
const thirdPlaceRoundId = 3;
|
||||
|
||||
const match = (
|
||||
id: number,
|
||||
roundId: number,
|
||||
number: number,
|
||||
opponent1: number | null,
|
||||
opponent2: number | null,
|
||||
winnerSide: MatchData["winnerSide"],
|
||||
): MatchData => ({
|
||||
id,
|
||||
stageId,
|
||||
groupId: roundId === thirdPlaceRoundId ? 1 : 0,
|
||||
roundId,
|
||||
number,
|
||||
opponent1: opponent1 === null ? null : { id: opponent1 },
|
||||
opponent2: opponent2 === null ? null : { id: opponent2 },
|
||||
winnerSide,
|
||||
});
|
||||
|
||||
return {
|
||||
stage: [
|
||||
{
|
||||
id: stageId,
|
||||
type: "single_elimination",
|
||||
settings: { consolationFinal: true },
|
||||
number: 1,
|
||||
},
|
||||
],
|
||||
group: [
|
||||
{ id: 0, stageId, number: 1 },
|
||||
{ id: 1, stageId, number: 2 },
|
||||
],
|
||||
round: [
|
||||
{ id: 0, stageId, groupId: 0, number: 1 },
|
||||
{ id: 1, stageId, groupId: 0, number: 2 },
|
||||
{ id: 2, stageId, groupId: 0, number: 3 },
|
||||
{ id: thirdPlaceRoundId, stageId, groupId: 1, number: 1 },
|
||||
],
|
||||
match: [
|
||||
match(0, 0, 1, 1, 2, "opponent1"),
|
||||
match(1, 0, 2, 3, 4, "opponent1"),
|
||||
match(2, 0, 3, 5, 6, "opponent1"),
|
||||
// six teams in an eight team bracket, both byes landed here
|
||||
match(3, 0, 4, null, null, null),
|
||||
match(4, 1, 1, 1, 3, "opponent1"),
|
||||
// won against a bye
|
||||
match(5, 1, 2, 5, null, "opponent1"),
|
||||
match(6, 2, 1, 1, 5, "opponent1"),
|
||||
// only one semifinal produced a loser
|
||||
match(7, thirdPlaceRoundId, 1, 3, null, null),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const legacyByeTournament = () =>
|
||||
testTournament({
|
||||
ctx: {
|
||||
settings: {
|
||||
bracketProgression: [
|
||||
{
|
||||
type: "single_elimination",
|
||||
name: "SE",
|
||||
requiresCheckIn: false,
|
||||
settings: {},
|
||||
sources: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
data: legacyByeBracketData(),
|
||||
});
|
||||
|
||||
it("places every team when a match is won against a bye", () => {
|
||||
const tournament = legacyByeTournament();
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
expect(standings.map((s) => [s.team.id, s.placement])).toEqual([
|
||||
[1, 1],
|
||||
[5, 2],
|
||||
[3, 3],
|
||||
[2, 4],
|
||||
[4, 4],
|
||||
[6, 4],
|
||||
]);
|
||||
});
|
||||
|
||||
it("gives third place to the only semifinal loser when the third place match is a bye", () => {
|
||||
const tournament = legacyByeTournament();
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
expect(standings.find((s) => s.team.id === 3)?.placement).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("single elimination standings - projected ties", () => {
|
||||
// Two semifinal losers tie for 3rd (no consolation final). Reports only one
|
||||
// semifinal so the other is still in progress, mirroring the projected
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import * as R from "remeda";
|
|||
import type { Tables } from "~/db/tables";
|
||||
import type {
|
||||
BracketData,
|
||||
MatchData,
|
||||
RoundData,
|
||||
} from "~/features/tournament-bracket/core/engine/types";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
|
@ -86,6 +87,9 @@ export class SingleEliminationBracket extends Bracket {
|
|||
continue;
|
||||
}
|
||||
|
||||
// BYE
|
||||
if (!match.opponent1 || !match.opponent2) continue;
|
||||
|
||||
const loser =
|
||||
match.winnerSide === "opponent1" ? match.opponent2 : match.opponent1;
|
||||
invariant(loser?.id, "Loser id not found");
|
||||
|
|
@ -139,12 +143,7 @@ export class SingleEliminationBracket extends Bracket {
|
|||
const thirdPlaceMatch = this.hasThirdPlaceMatch()
|
||||
? this.data.match.find((m) => m.groupId !== matches[0].groupId)
|
||||
: undefined;
|
||||
const thirdPlaceMatchWinner =
|
||||
thirdPlaceMatch?.winnerSide === "opponent1"
|
||||
? thirdPlaceMatch.opponent1
|
||||
: thirdPlaceMatch?.winnerSide === "opponent2"
|
||||
? thirdPlaceMatch.opponent2
|
||||
: undefined;
|
||||
const thirdPlaceMatchWinner = winnerOfThirdPlaceMatch(thirdPlaceMatch);
|
||||
|
||||
const resultWithThirdPlaceTiebroken = result
|
||||
.flatMap((standing) => {
|
||||
|
|
@ -220,3 +219,20 @@ export class SingleEliminationBracket extends Bracket {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A third place match with only one opponent is decided by a BYE: the semifinal
|
||||
* on the other side was itself won against a BYE, so it produced no loser and
|
||||
* the lone semifinal loser takes third place without playing.
|
||||
*/
|
||||
function winnerOfThirdPlaceMatch(match: MatchData | undefined) {
|
||||
if (!match) return undefined;
|
||||
|
||||
if (match.opponent1 && !match.opponent2) return match.opponent1;
|
||||
if (!match.opponent1 && match.opponent2) return match.opponent2;
|
||||
|
||||
if (match.winnerSide === "opponent1") return match.opponent1;
|
||||
if (match.winnerSide === "opponent2") return match.opponent2;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user