mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-28 14:20:46 -05:00
Fix SE quarterfinal losers showing as losers while 3rd place match in progress
This commit is contained in:
@@ -65,6 +65,24 @@ export class SingleEliminationBracket extends Bracket {
|
||||
return R.unique(this.data.match.map((m) => m.groupId)).length > 1;
|
||||
}
|
||||
|
||||
private thirdPlaceMatch() {
|
||||
if (!this.hasThirdPlaceMatch()) return undefined;
|
||||
|
||||
const thirdPlaceGroupId = Math.max(
|
||||
...this.data.group.map((group) => group.id),
|
||||
);
|
||||
|
||||
return this.data.match.find((match) => match.groupId === thirdPlaceGroupId);
|
||||
}
|
||||
|
||||
private thirdPlaceMatchUndecided() {
|
||||
const thirdPlaceMatch = this.thirdPlaceMatch();
|
||||
|
||||
return (
|
||||
Boolean(thirdPlaceMatch) && !winnerOfThirdPlaceMatch(thirdPlaceMatch)
|
||||
);
|
||||
}
|
||||
|
||||
protected calculateStandings(): Standing[] {
|
||||
const teams: { id: number; lostAt: number }[] = [];
|
||||
|
||||
@@ -140,20 +158,18 @@ export class SingleEliminationBracket extends Bracket {
|
||||
});
|
||||
}
|
||||
|
||||
const thirdPlaceMatch = this.hasThirdPlaceMatch()
|
||||
? this.data.match.find((m) => m.groupId !== matches[0].groupId)
|
||||
: undefined;
|
||||
const thirdPlaceMatchWinner = winnerOfThirdPlaceMatch(thirdPlaceMatch);
|
||||
const thirdPlaceMatchWinner = winnerOfThirdPlaceMatch(
|
||||
this.thirdPlaceMatch(),
|
||||
);
|
||||
|
||||
const resultWithThirdPlaceTiebroken = result
|
||||
.flatMap((standing) => {
|
||||
if (standing.placement !== 3 || !thirdPlaceMatch) return [standing];
|
||||
// semifinal losers have not finished their run before the third place match is played
|
||||
if (!thirdPlaceMatchWinner) return [];
|
||||
if (thirdPlaceMatchWinner.id !== standing.team.id) {
|
||||
return [{ ...standing, placement: 4 }];
|
||||
}
|
||||
return [standing];
|
||||
.map((standing) => {
|
||||
// semifinal losers stay tied until the third place match decides between them
|
||||
if (standing.placement !== 3 || !thirdPlaceMatchWinner) return standing;
|
||||
|
||||
return thirdPlaceMatchWinner.id === standing.team.id
|
||||
? standing
|
||||
: { ...standing, placement: 4 };
|
||||
})
|
||||
.sort((a, b) => a.placement - b.placement);
|
||||
|
||||
@@ -169,7 +185,12 @@ export class SingleEliminationBracket extends Bracket {
|
||||
);
|
||||
|
||||
if (placements.every((placement) => placement > 0)) {
|
||||
return this.sourceByStandings(placements, rest === true);
|
||||
const source = this.sourceByStandings(placements, rest === true);
|
||||
|
||||
// 3rd and 4th are only decided once the third place match is played
|
||||
return this.thirdPlaceMatchUndecided()
|
||||
? { ...source, relevantMatchesFinished: false }
|
||||
: source;
|
||||
}
|
||||
|
||||
// third place match lives in a separate (higher) group, the lowest group id is the winners group
|
||||
|
||||
@@ -165,6 +165,33 @@ describe("tournamentStandings", () => {
|
||||
1, 2, 3, 4, 5, 5, 5, 5,
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps quarterfinal losers 5th while the third place match is still pending", () => {
|
||||
const result = tournamentStandings(
|
||||
singleEliminationWithPendingThirdPlaceMatch(),
|
||||
);
|
||||
invariant(result.type === "single");
|
||||
|
||||
const placementOf = (teamId: number) =>
|
||||
result.standings.find((standing) => standing.team.id === teamId)
|
||||
?.placement;
|
||||
|
||||
expect(placementOf(1)).toBe(1);
|
||||
expect(placementOf(2)).toBe(2);
|
||||
expect([5, 6, 7, 8].map(placementOf)).toEqual([5, 5, 5, 5]);
|
||||
});
|
||||
|
||||
test("lists the semifinal losers while the third place match is still pending", () => {
|
||||
const result = tournamentStandings(
|
||||
singleEliminationWithPendingThirdPlaceMatch(),
|
||||
);
|
||||
invariant(result.type === "single");
|
||||
|
||||
const teamIds = result.standings.map((standing) => standing.team.id);
|
||||
|
||||
expect(teamIds).toContain(3);
|
||||
expect(teamIds).toContain(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reNumberPlacements", () => {
|
||||
@@ -517,6 +544,54 @@ function singleEliminationTournament() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Eight teams, every match reported except the third place match. */
|
||||
function singleEliminationWithPendingThirdPlaceMatch() {
|
||||
let data = createResolved({
|
||||
type: "single_elimination",
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: { consolationFinal: true },
|
||||
});
|
||||
const thirdPlaceGroupId = Math.max(...data.group.map((group) => group.id));
|
||||
|
||||
while (true) {
|
||||
const pending = data.match.find(
|
||||
(match) =>
|
||||
match.groupId !== thirdPlaceGroupId &&
|
||||
typeof match.opponent1?.id === "number" &&
|
||||
typeof match.opponent2?.id === "number" &&
|
||||
!match.winnerSide,
|
||||
);
|
||||
if (!pending) break;
|
||||
|
||||
const winnerIsOpp1 =
|
||||
(pending.opponent1!.id as number) < (pending.opponent2!.id as number);
|
||||
data = Engine.reportResult(data, {
|
||||
matchId: pending.id,
|
||||
scores: [winnerIsOpp1 ? 2 : 0, winnerIsOpp1 ? 0 : 2],
|
||||
winnerSide: winnerIsOpp1 ? "opponent1" : "opponent2",
|
||||
}).data;
|
||||
}
|
||||
|
||||
return testTournament({
|
||||
ctx: {
|
||||
settings: {
|
||||
bracketProgression: [
|
||||
{
|
||||
type: "single_elimination",
|
||||
name: "Main Bracket",
|
||||
requiresCheckIn: false,
|
||||
settings: { thirdPlaceMatch: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
teams: [1, 2, 3, 4, 5, 6, 7, 8].map((id) =>
|
||||
tournamentCtxTeam(id, { seed: id }),
|
||||
),
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
function singleEliminationWithUndergroundTournament({
|
||||
undergroundSeeding = [5, 6, 7, 8],
|
||||
undergroundConsolationFinal = undergroundSeeding.length > 2,
|
||||
|
||||
5
changelog/2026-09-02-third-place-match-standings.md
Normal file
5
changelog/2026-09-02-third-place-match-standings.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
navItem: calendar
|
||||
type: bug
|
||||
---
|
||||
Tournament results no longer show the quarterfinal losers as 3rd while the third place match is still being played
|
||||
Reference in New Issue
Block a user