mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 07:37:55 -05:00
Port "Play final and consolation final in parallel"
4b61707c2d (diff-62b417fe480c807f60ec4a160e2c3a621587b08c6d674fc0ee5bdd8d8cd73f84)
This commit is contained in:
@@ -108,7 +108,7 @@ export class BaseGetter {
|
||||
return this.getPreviousMatchesLB(match, stage, roundNumber);
|
||||
|
||||
if (matchLocation === "final_group")
|
||||
return this.getPreviousMatchesFinal(match, roundNumber);
|
||||
return this.getPreviousMatchesFinal(match, stage, roundNumber);
|
||||
|
||||
if (roundNumber === 1) return []; // The match is in the first round of an upper bracket.
|
||||
|
||||
@@ -119,23 +119,89 @@ export class BaseGetter {
|
||||
* Gets the matches leading to the given match, which is in a final group (consolation final or grand final).
|
||||
*
|
||||
* @param match The current match.
|
||||
* @param stage The parent stage.
|
||||
* @param roundNumber Number of the current round.
|
||||
*/
|
||||
private getPreviousMatchesFinal(match: Match, roundNumber: number): Match[] {
|
||||
private getPreviousMatchesFinal(
|
||||
match: Match,
|
||||
stage: Stage,
|
||||
roundNumber: number
|
||||
): Match[] {
|
||||
if (stage.type === "single_elimination")
|
||||
return this.getPreviousMatchesFinalSingleElimination(match, stage);
|
||||
|
||||
return this.getPreviousMatchesFinalDoubleElimination(match, roundNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the matches leading to the given match, which is in a final group (consolation final).
|
||||
*
|
||||
* @param match The current match.
|
||||
* @param stage The parent stage.
|
||||
*/
|
||||
private getPreviousMatchesFinalSingleElimination(
|
||||
match: Match,
|
||||
stage: Stage
|
||||
): Match[] {
|
||||
const upperBracket = this.getUpperBracket(match.stage_id);
|
||||
const upperBracketRoundCount = helpers.getUpperBracketRoundCount(
|
||||
stage.settings.size!
|
||||
);
|
||||
|
||||
const semiFinalsRound = this.storage.selectFirst("round", {
|
||||
group_id: upperBracket.id,
|
||||
number: upperBracketRoundCount - 1, // Second to last round
|
||||
});
|
||||
|
||||
if (!semiFinalsRound) throw Error("Semi finals round not found.");
|
||||
|
||||
const semiFinalMatches = this.storage.select("match", {
|
||||
round_id: semiFinalsRound.id,
|
||||
});
|
||||
|
||||
if (!semiFinalMatches) throw Error("Error getting semi final matches.");
|
||||
|
||||
// In single elimination, both the final and consolation final have the same previous matches.
|
||||
return semiFinalMatches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the matches leading to the given match, which is in a final group (grand final).
|
||||
*
|
||||
* @param match The current match.
|
||||
* @param stage The parent stage.
|
||||
* @param roundNumber Number of the current round.
|
||||
*/
|
||||
private getPreviousMatchesFinalDoubleElimination(
|
||||
match: Match,
|
||||
roundNumber: number
|
||||
): Match[] {
|
||||
if (roundNumber > 1)
|
||||
// Double grand final
|
||||
return [this.findMatch(match.group_id, roundNumber - 1, 1)];
|
||||
|
||||
const upperBracket = this.getUpperBracket(match.stage_id);
|
||||
const lastRound = this.getLastRound(upperBracket.id);
|
||||
const winnerBracket = this.getUpperBracket(match.stage_id);
|
||||
const lastRoundWB = this.getLastRound(winnerBracket.id);
|
||||
|
||||
const upperBracketFinalMatch = this.storage.selectFirst("match", {
|
||||
round_id: lastRound.id,
|
||||
const winnerBracketFinalMatch = this.storage.selectFirst("match", {
|
||||
round_id: lastRoundWB.id,
|
||||
number: 1,
|
||||
});
|
||||
|
||||
if (upperBracketFinalMatch === null) throw Error("Match not found.");
|
||||
if (!winnerBracketFinalMatch) throw Error("Match not found.");
|
||||
|
||||
return [upperBracketFinalMatch];
|
||||
const loserBracket = this.getLoserBracket(match.stage_id);
|
||||
if (!loserBracket) throw Error("Loser bracket not found.");
|
||||
|
||||
const lastRoundLB = this.getLastRound(loserBracket.id);
|
||||
const loserBracketFinalMatch = this.storage.selectFirst("match", {
|
||||
round_id: lastRoundLB.id,
|
||||
number: 1,
|
||||
});
|
||||
|
||||
if (!loserBracketFinalMatch) throw Error("Match not found.");
|
||||
|
||||
return [winnerBracketFinalMatch, loserBracketFinalMatch];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -284,6 +284,8 @@ export class BaseUpdater extends BaseGetter {
|
||||
*/
|
||||
protected archiveMatches(matches: Match[]): void {
|
||||
for (const match of matches) {
|
||||
if (match.status === Status.Archived) continue;
|
||||
|
||||
match.status = Status.Archived;
|
||||
this.applyMatchUpdate(match);
|
||||
}
|
||||
@@ -324,7 +326,13 @@ export class BaseUpdater extends BaseGetter {
|
||||
roundNumber,
|
||||
roundCount
|
||||
);
|
||||
if (nextMatches.length === 0) return;
|
||||
if (nextMatches.length === 0) {
|
||||
// Archive match if it doesn't have following matches and is completed.
|
||||
// When the stage is fully complete, all matches should be archived.
|
||||
if (match.status === Status.Completed) this.archiveMatches([match]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const winnerSide = helpers.getMatchResult(match);
|
||||
const actualRoundNumber =
|
||||
|
||||
@@ -160,18 +160,6 @@ ExtraFields("Extra fields when updating a match game", () => {
|
||||
// @ts-expect-error incomplete types
|
||||
foo: 42, // Extra field.
|
||||
},
|
||||
opponent2: {
|
||||
score: 1,
|
||||
result: "loss",
|
||||
},
|
||||
});
|
||||
|
||||
manager.update.matchGame({
|
||||
id: 2,
|
||||
opponent1: {
|
||||
score: 3,
|
||||
result: "win",
|
||||
},
|
||||
opponent2: {
|
||||
score: 1,
|
||||
result: "loss",
|
||||
@@ -182,7 +170,7 @@ ExtraFields("Extra fields when updating a match game", () => {
|
||||
|
||||
assert.equal(storage.select<any>("match_game", 0).weather, "rainy");
|
||||
assert.equal(storage.select<any>("match_game", 1).opponent1.foo, 42);
|
||||
assert.equal(storage.select<any>("match_game", 2).opponent2.info, {
|
||||
assert.equal(storage.select<any>("match_game", 1).opponent2.info, {
|
||||
replacements: [1, 2],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -288,6 +288,21 @@ MatchUpdateDoubleElimination("should determine matches in grand final", () => {
|
||||
storage.select<any>("match", 6).opponent2.id, // Determined opponent for the grand final (round 2)
|
||||
storage.select<any>("match", 1).opponent2.id // Winner of LB Final
|
||||
);
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Archived);
|
||||
assert.equal(storage.select<any>("match", 4).status, Status.Archived);
|
||||
|
||||
assert.equal(storage.select<any>("match", 5).status, Status.Completed); // Grand final (round 1)
|
||||
assert.equal(storage.select<any>("match", 6).status, Status.Ready); // Grand final (round 2)
|
||||
|
||||
manager.update.match({
|
||||
id: 6, // Grand Final round 2
|
||||
opponent1: { score: 16, result: "win" },
|
||||
opponent2: { score: 10 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 5).status, Status.Archived); // Grand final (round 1)
|
||||
assert.equal(storage.select<any>("match", 6).status, Status.Archived); // Grand final (round 2)
|
||||
});
|
||||
|
||||
MatchUpdateDoubleElimination(
|
||||
|
||||
@@ -45,9 +45,9 @@ CreateSingleEliminationStage("should create a single elimination stage", () => {
|
||||
assert.equal(stage.name, example.name);
|
||||
assert.equal(stage.type, example.type);
|
||||
|
||||
assert.equal(storage.select("group")!.length, 1);
|
||||
assert.equal(storage.select("round")!.length, 4);
|
||||
assert.equal(storage.select("match")!.length, 15);
|
||||
assert.equal(storage.select<any>("group")!.length, 1);
|
||||
assert.equal(storage.select<any>("round")!.length, 4);
|
||||
assert.equal(storage.select<any>("match")!.length, 15);
|
||||
});
|
||||
|
||||
CreateSingleEliminationStage(
|
||||
@@ -97,9 +97,9 @@ CreateSingleEliminationStage(
|
||||
settings: { consolationFinal: true, seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
assert.equal(storage.select("group")!.length, 2);
|
||||
assert.equal(storage.select("round")!.length, 4);
|
||||
assert.equal(storage.select("match")!.length, 8);
|
||||
assert.equal(storage.select<any>("group")!.length, 2);
|
||||
assert.equal(storage.select<any>("round")!.length, 4);
|
||||
assert.equal(storage.select<any>("match")!.length, 8);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -152,10 +152,10 @@ CreateSingleEliminationStage(
|
||||
settings: { seedOrdering: ["natural"], matchesChildCount: 3 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select("group")!.length, 1);
|
||||
assert.equal(storage.select("round")!.length, 3);
|
||||
assert.equal(storage.select("match")!.length, 7);
|
||||
assert.equal(storage.select("match_game")!.length, 7 * 3);
|
||||
assert.equal(storage.select<any>("group")!.length, 1);
|
||||
assert.equal(storage.select<any>("round")!.length, 3);
|
||||
assert.equal(storage.select<any>("match")!.length, 7);
|
||||
assert.equal(storage.select<any>("match_game")!.length, 7 * 3);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -320,12 +320,6 @@ PreviousAndNextMatchUpdate(
|
||||
opponent2: { score: 16, result: "win" },
|
||||
});
|
||||
|
||||
manager.update.match({
|
||||
id: 2, // Final
|
||||
opponent1: { score: 16, result: "win" },
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
storage.select<any>("match", 3).opponent1.id, // Determined opponent for the consolation final
|
||||
storage.select<any>("match", 0).opponent2.id // Loser of Semi 1
|
||||
@@ -335,6 +329,70 @@ PreviousAndNextMatchUpdate(
|
||||
storage.select<any>("match", 3).opponent2.id, // Determined opponent for the consolation final
|
||||
storage.select<any>("match", 1).opponent1.id // Loser of Semi 2
|
||||
);
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Ready);
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Ready);
|
||||
}
|
||||
);
|
||||
|
||||
PreviousAndNextMatchUpdate(
|
||||
"should play both the final and consolation final in parallel",
|
||||
() => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
settings: { consolationFinal: true },
|
||||
});
|
||||
|
||||
manager.update.match({
|
||||
id: 0, // First match of round 1
|
||||
opponent1: { score: 16, result: "win" },
|
||||
opponent2: { score: 12 },
|
||||
});
|
||||
|
||||
manager.update.match({
|
||||
id: 1, // Second match of round 1
|
||||
opponent1: { score: 13 },
|
||||
opponent2: { score: 16, result: "win" },
|
||||
});
|
||||
|
||||
manager.update.match({
|
||||
id: 2, // Final
|
||||
opponent1: { score: 12 },
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Running);
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Ready);
|
||||
|
||||
manager.update.match({
|
||||
id: 3, // Consolation final
|
||||
opponent1: { score: 12 },
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Running);
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Running);
|
||||
|
||||
manager.update.match({
|
||||
id: 3, // Consolation final
|
||||
opponent1: { score: 16, result: "win" },
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Running);
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Archived);
|
||||
|
||||
manager.update.match({
|
||||
id: 2, // Final
|
||||
opponent1: { score: 16, result: "win" },
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Archived);
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Archived);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -374,7 +432,8 @@ PreviousAndNextMatchUpdate("should archive previous matches", () => {
|
||||
opponent2: { score: 9 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Archived);
|
||||
assert.equal(storage.select<any>("match", 2).status, Status.Archived); // Final
|
||||
assert.equal(storage.select<any>("match", 3).status, Status.Archived); // Consolation final
|
||||
});
|
||||
|
||||
CreateSingleEliminationStage.run();
|
||||
|
||||
@@ -489,7 +489,7 @@ UpdateMatchGames(
|
||||
});
|
||||
|
||||
finalMatchStatus = storage.select<any>("match", 2).status;
|
||||
assert.equal(finalMatchStatus, Status.Completed);
|
||||
assert.equal(finalMatchStatus, Status.Archived);
|
||||
assert.equal(finalMatchStatus, storage.select<any>("match_game", 4).status);
|
||||
|
||||
const semi1Status = storage.select<any>("match", 0).status;
|
||||
@@ -742,7 +742,7 @@ UpdateMatchGames(
|
||||
|
||||
manager.update.matchGame({ id: 0, opponent1: { result: "win" } });
|
||||
manager.update.matchGame({ id: 1, opponent1: { result: "win" } });
|
||||
assert.equal(storage.select<any>("match", 0).status, Status.Completed);
|
||||
assert.equal(storage.select<any>("match", 0).status, Status.Archived); // Completed, but single match in the stage.
|
||||
|
||||
manager.reset.matchGameResults(0);
|
||||
assert.equal(storage.select<any>("match", 0).status, Status.Running);
|
||||
|
||||
Reference in New Issue
Block a user