mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Remove unused participant code + slim down tournament response
This commit is contained in:
parent
4248cca5b1
commit
dcf738f6d3
|
|
@ -14,7 +14,7 @@ import type { BracketMapCounts } from "./toMapList";
|
|||
interface CreateBracketArgs {
|
||||
id: number;
|
||||
preview: boolean;
|
||||
data: ValueToArray<DataTypes>;
|
||||
data: Omit<ValueToArray<DataTypes>, "participant">;
|
||||
type: Tables["TournamentStage"]["type"];
|
||||
canBeStarted?: boolean;
|
||||
name: string;
|
||||
|
|
@ -25,10 +25,7 @@ interface CreateBracketArgs {
|
|||
bracketIdx: number;
|
||||
placements: number[];
|
||||
}[];
|
||||
seeding?: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
seeding?: number[];
|
||||
}
|
||||
|
||||
export interface Standing {
|
||||
|
|
@ -219,6 +216,18 @@ export abstract class Bracket {
|
|||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
get participantTournamentTeamIds() {
|
||||
// if (this.seeding) {
|
||||
// return this.seeding.map((seed) => seed.id);
|
||||
// }
|
||||
|
||||
return removeDuplicates(
|
||||
this.data.match
|
||||
.flatMap((match) => [match.opponent1?.id, match.opponent2?.id])
|
||||
.filter(Boolean),
|
||||
) as number[];
|
||||
}
|
||||
|
||||
currentStandings(_includeUnfinishedGroups: boolean) {
|
||||
return this.standings;
|
||||
}
|
||||
|
|
@ -272,7 +281,10 @@ export abstract class Bracket {
|
|||
}
|
||||
|
||||
get enoughTeams() {
|
||||
return this.data.participant.length >= TOURNAMENT.ENOUGH_TEAMS_TO_START;
|
||||
return (
|
||||
this.participantTournamentTeamIds.length >=
|
||||
TOURNAMENT.ENOUGH_TEAMS_TO_START
|
||||
);
|
||||
}
|
||||
|
||||
canCheckIn(user: OptionalIdObject) {
|
||||
|
|
@ -287,14 +299,14 @@ export abstract class Bracket {
|
|||
|
||||
source(_placements: number[]): {
|
||||
relevantMatchesFinished: boolean;
|
||||
teams: { id: number; name: string }[];
|
||||
teams: number[];
|
||||
} {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
teamsWithNames(teams: { id: number }[]) {
|
||||
return teams.map((team) => {
|
||||
const name = this.data.participant.find(
|
||||
const name = this.tournament.ctx.teams.find(
|
||||
(participant) => participant.id === team.id,
|
||||
)?.name;
|
||||
invariant(name, `Team name not found for id: ${team.id}`);
|
||||
|
|
@ -420,7 +432,7 @@ class SingleEliminationBracket extends Bracket {
|
|||
}
|
||||
|
||||
const teamCountWhoDidntLoseYet =
|
||||
this.data.participant.length - teams.length;
|
||||
this.participantTournamentTeamIds.length - teams.length;
|
||||
|
||||
const result: Standing[] = [];
|
||||
for (const roundId of removeDuplicates(teams.map((team) => team.lostAt))) {
|
||||
|
|
@ -443,13 +455,13 @@ class SingleEliminationBracket extends Bracket {
|
|||
}
|
||||
|
||||
if (teamCountWhoDidntLoseYet === 1) {
|
||||
const winner = this.data.participant.find((participant) =>
|
||||
result.every(({ team }) => team.id !== participant.id),
|
||||
const winnerId = this.participantTournamentTeamIds.find((participantId) =>
|
||||
result.every(({ team }) => team.id !== participantId),
|
||||
);
|
||||
invariant(winner, "No winner identified");
|
||||
invariant(winnerId, "No winner identified");
|
||||
|
||||
const winnerTeam = this.tournament.teamById(winner.id);
|
||||
invariant(winnerTeam, `Winner team not found for id: ${winner.id}`);
|
||||
const winnerTeam = this.tournament.teamById(winnerId);
|
||||
invariant(winnerTeam, `Winner team not found for id: ${winnerId}`);
|
||||
|
||||
result.push({
|
||||
team: winnerTeam,
|
||||
|
|
@ -573,7 +585,7 @@ class DoubleEliminationBracket extends Bracket {
|
|||
}
|
||||
|
||||
const teamCountWhoDidntLoseInLosersYet =
|
||||
this.data.participant.length - teams.length;
|
||||
this.participantTournamentTeamIds.length - teams.length;
|
||||
|
||||
const result: Standing[] = [];
|
||||
for (const roundId of removeDuplicates(teams.map((team) => team.lostAt))) {
|
||||
|
|
@ -691,13 +703,15 @@ class DoubleEliminationBracket extends Bracket {
|
|||
}
|
||||
|
||||
source(placements: number[]) {
|
||||
const resolveLosersGroupId = (data: ValueToArray<DataTypes>) => {
|
||||
const resolveLosersGroupId = (
|
||||
data: Omit<ValueToArray<DataTypes>, "participant">,
|
||||
) => {
|
||||
const minGroupId = Math.min(...data.round.map((round) => round.group_id));
|
||||
|
||||
return minGroupId + 1;
|
||||
};
|
||||
const placementsToRoundsIds = (
|
||||
data: ValueToArray<DataTypes>,
|
||||
data: Omit<ValueToArray<DataTypes>, "participant">,
|
||||
losersGroupId: number,
|
||||
) => {
|
||||
const firstRoundIsOnlyByes = () => {
|
||||
|
|
@ -742,7 +756,7 @@ class DoubleEliminationBracket extends Bracket {
|
|||
(a, b) => b - a,
|
||||
);
|
||||
|
||||
const teams: { id: number }[] = [];
|
||||
const teams: number[] = [];
|
||||
let relevantMatchesFinished = true;
|
||||
for (const roundId of sourceRoundsIds) {
|
||||
const roundsMatches = this.data.match.filter(
|
||||
|
|
@ -766,13 +780,13 @@ class DoubleEliminationBracket extends Bracket {
|
|||
match.opponent1?.result === "win" ? match.opponent2 : match.opponent1;
|
||||
invariant(loser?.id, "Loser id not found");
|
||||
|
||||
teams.push({ id: loser.id });
|
||||
teams.push(loser.id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
relevantMatchesFinished,
|
||||
teams: this.teamsWithNames(teams),
|
||||
teams,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -788,14 +802,14 @@ class RoundRobinBracket extends Bracket {
|
|||
|
||||
source(placements: number[]): {
|
||||
relevantMatchesFinished: boolean;
|
||||
teams: { id: number; name: string }[];
|
||||
teams: number[];
|
||||
} {
|
||||
if (placements.some((p) => p < 0)) {
|
||||
throw new Error("Negative placements not implemented");
|
||||
}
|
||||
const standings = this.standings;
|
||||
const relevantMatchesFinished =
|
||||
standings.length === this.data.participant.length;
|
||||
standings.length === this.participantTournamentTeamIds.length;
|
||||
|
||||
const uniquePlacements = removeDuplicates(
|
||||
standings.map((s) => s.placement),
|
||||
|
|
@ -810,7 +824,7 @@ class RoundRobinBracket extends Bracket {
|
|||
relevantMatchesFinished,
|
||||
teams: standings
|
||||
.filter((s) => placements.includes(placementNormalized(s.placement)))
|
||||
.map((s) => ({ id: s.team.id, name: s.team.name })),
|
||||
.map((s) => s.team.id),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1060,7 +1074,7 @@ class SwissBracket extends Bracket {
|
|||
|
||||
source(placements: number[]): {
|
||||
relevantMatchesFinished: boolean;
|
||||
teams: { id: number; name: string }[];
|
||||
teams: number[];
|
||||
} {
|
||||
if (placements.some((p) => p < 0)) {
|
||||
throw new Error("Negative placements not implemented");
|
||||
|
|
@ -1101,7 +1115,7 @@ class SwissBracket extends Bracket {
|
|||
relevantMatchesFinished,
|
||||
teams: standings
|
||||
.filter((s) => placements.includes(placementNormalized(s.placement)))
|
||||
.map((s) => ({ id: s.team.id, name: s.team.name })),
|
||||
.map((s) => s.team.id),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,9 @@ import { nullFilledArray } from "~/utils/arrays";
|
|||
import type { Bracket, Standing } from "./Bracket";
|
||||
import type { TournamentRepositoryInsertableMatch } from "~/features/tournament/TournamentRepository.server";
|
||||
|
||||
type SwissSeeding = { id: number; name: string };
|
||||
|
||||
interface CreateArgs extends Omit<InputStage, "type" | "seeding" | "number"> {
|
||||
seeding: readonly SwissSeeding[];
|
||||
}
|
||||
|
||||
export function create(args: CreateArgs): ValueToArray<DataTypes> {
|
||||
export function create(
|
||||
args: Omit<InputStage, "type" | "number">,
|
||||
): ValueToArray<DataTypes> {
|
||||
const swissSettings = args.settings?.swiss;
|
||||
|
||||
const groupCount = swissSettings?.groupCount ?? 1;
|
||||
|
|
@ -29,11 +25,6 @@ export function create(args: CreateArgs): ValueToArray<DataTypes> {
|
|||
return {
|
||||
group,
|
||||
match: firstRoundMatches({ seeding: args.seeding, groupCount, roundCount }),
|
||||
participant: args.seeding.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
tournament_id: args.tournamentId,
|
||||
})),
|
||||
round: group.flatMap((g) =>
|
||||
nullFilledArray(roundCount).map((_, i) => ({
|
||||
id: roundId++,
|
||||
|
|
@ -60,7 +51,7 @@ function firstRoundMatches({
|
|||
groupCount,
|
||||
roundCount,
|
||||
}: {
|
||||
seeding: CreateArgs["seeding"];
|
||||
seeding: InputStage["seeding"];
|
||||
groupCount: number;
|
||||
roundCount: number;
|
||||
}): Match[] {
|
||||
|
|
@ -106,10 +97,10 @@ function firstRoundMatches({
|
|||
round_id: roundId,
|
||||
number: i + 1,
|
||||
opponent1: {
|
||||
id: upper.id,
|
||||
id: upper,
|
||||
},
|
||||
opponent2: {
|
||||
id: lower.id,
|
||||
id: lower,
|
||||
},
|
||||
status: 2,
|
||||
});
|
||||
|
|
@ -123,7 +114,7 @@ function firstRoundMatches({
|
|||
round_id: roundId,
|
||||
number: upperHalf.length + 1,
|
||||
opponent1: {
|
||||
id: bye.id,
|
||||
id: bye,
|
||||
},
|
||||
opponent2: null,
|
||||
status: 2,
|
||||
|
|
@ -137,11 +128,11 @@ function firstRoundMatches({
|
|||
if (!seeding) return [];
|
||||
if (groupCount === 1) return [[...seeding]];
|
||||
|
||||
const groups: SwissSeeding[][] = nullFilledArray(groupCount).map(() => []);
|
||||
const groups: number[][] = nullFilledArray(groupCount).map(() => []);
|
||||
|
||||
for (let i = 0; i < seeding.length; i++) {
|
||||
const groupIndex = i % groupCount;
|
||||
groups[groupIndex].push(seeding[i]);
|
||||
groups[groupIndex].push(seeding[i]!);
|
||||
}
|
||||
|
||||
return groups;
|
||||
|
|
|
|||
|
|
@ -23,22 +23,18 @@ FollowUp("includes correct teams in the top cut", () => {
|
|||
for (const tournamentTeamId of [892, 882, 881]) {
|
||||
assert.ok(
|
||||
tournamentPP257.brackets[1].seeding?.some(
|
||||
(team) => team.id === tournamentTeamId,
|
||||
(team) => team === tournamentTeamId,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
FollowUp("underground bracket includes a checked in team", () => {
|
||||
assert.ok(
|
||||
tournamentPP257.brackets[2].seeding?.some((team) => team.id === 902),
|
||||
);
|
||||
assert.ok(tournamentPP257.brackets[2].seeding?.some((team) => team === 902));
|
||||
});
|
||||
|
||||
FollowUp("underground bracket doesn't include a non checked in team", () => {
|
||||
assert.ok(
|
||||
tournamentPP257.brackets[2].seeding?.some((team) => team.id === 902),
|
||||
);
|
||||
assert.ok(tournamentPP257.brackets[2].seeding?.some((team) => team === 902));
|
||||
});
|
||||
|
||||
FollowUp("underground bracket includes checked in teams (DE->SE)", () => {
|
||||
|
|
@ -141,7 +137,7 @@ FollowUp("avoids rematches in RR -> SE (PP 255)", () => {
|
|||
validateNoRematches(rrMatches, topCutMatches);
|
||||
});
|
||||
|
||||
FollowUp("avoids rematches in RR -> SE (PP 255)", () => {
|
||||
FollowUp("avoids rematches in RR -> SE (PP 255) - only minimum swap", () => {
|
||||
const oldTopCutMatches = PADDLING_POOL_255_TOP_CUT_INITIAL_MATCHES();
|
||||
const newTopCutMatches = tournamentPP255.brackets[1].data.match;
|
||||
|
||||
|
|
@ -164,7 +160,7 @@ FollowUp("avoids rematches in RR -> SE (PP 255)", () => {
|
|||
}
|
||||
|
||||
// 1 team should get swapped meaning two matches are now different
|
||||
assert.equal(different, 2);
|
||||
assert.equal(different, 2, "Amount of different matches is incorrect");
|
||||
});
|
||||
|
||||
FollowUp.run();
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export class Tournament {
|
|||
return a.createdAt - b.createdAt;
|
||||
}
|
||||
|
||||
private initBrackets(data: ValueToArray<DataTypes>) {
|
||||
private initBrackets(data: Omit<ValueToArray<DataTypes>, "participant">) {
|
||||
for (const [
|
||||
bracketIdx,
|
||||
{ type, name, sources },
|
||||
|
|
@ -99,11 +99,6 @@ export class Tournament {
|
|||
const match = data.match.filter(
|
||||
(match) => match.stage_id === inProgressStage.id,
|
||||
);
|
||||
const participants = new Set(
|
||||
match
|
||||
.flatMap((match) => [match.opponent1?.id, match.opponent2?.id])
|
||||
.filter((id) => typeof id === "number"),
|
||||
);
|
||||
|
||||
this.brackets.push(
|
||||
Bracket.create({
|
||||
|
|
@ -115,9 +110,6 @@ export class Tournament {
|
|||
createdAt: inProgressStage.createdAt,
|
||||
data: {
|
||||
...data,
|
||||
participant: data.participant.filter((participant) =>
|
||||
participants.has(participant.id),
|
||||
),
|
||||
group: data.group.filter(
|
||||
(group) => group.stage_id === inProgressStage.id,
|
||||
),
|
||||
|
|
@ -136,7 +128,7 @@ export class Tournament {
|
|||
const { teams, relevantMatchesFinished } = sources
|
||||
? this.resolveTeamsFromSources(sources)
|
||||
: {
|
||||
teams: this.ctx.teams,
|
||||
teams: this.ctx.teams.map((team) => team.id),
|
||||
relevantMatchesFinished: true,
|
||||
};
|
||||
|
||||
|
|
@ -166,7 +158,7 @@ export class Tournament {
|
|||
checkedInTeams.length >= TOURNAMENT.ENOUGH_TEAMS_TO_START &&
|
||||
(sources ? relevantMatchesFinished : this.regularCheckInHasEnded),
|
||||
teamsPendingCheckIn:
|
||||
bracketIdx !== 0 ? notCheckedInTeams.map((t) => t.id) : undefined,
|
||||
bracketIdx !== 0 ? notCheckedInTeams : undefined,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -174,7 +166,7 @@ export class Tournament {
|
|||
const { teams, relevantMatchesFinished } = sources
|
||||
? this.resolveTeamsFromSources(sources)
|
||||
: {
|
||||
teams: this.ctx.teams,
|
||||
teams: this.ctx.teams.map((team) => team.id),
|
||||
relevantMatchesFinished: true,
|
||||
};
|
||||
|
||||
|
|
@ -225,7 +217,7 @@ export class Tournament {
|
|||
TOURNAMENT.ENOUGH_TEAMS_TO_START &&
|
||||
(sources ? relevantMatchesFinished : this.regularCheckInHasEnded),
|
||||
teamsPendingCheckIn:
|
||||
bracketIdx !== 0 ? notCheckedInTeams.map((t) => t.id) : undefined,
|
||||
bracketIdx !== 0 ? notCheckedInTeams : undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -235,7 +227,7 @@ export class Tournament {
|
|||
private resolveTeamsFromSources(
|
||||
sources: NonNullable<TournamentBracketProgression[number]["sources"]>,
|
||||
) {
|
||||
const teams: { id: number; name: string }[] = [];
|
||||
const teams: number[] = [];
|
||||
|
||||
let allRelevantMatchesFinished = true;
|
||||
for (const { bracketIdx, placements } of sources) {
|
||||
|
|
@ -254,10 +246,7 @@ export class Tournament {
|
|||
}
|
||||
|
||||
private avoidReplaysOfPreviousBracketOpponent(
|
||||
teams: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[],
|
||||
teams: number[],
|
||||
bracket: {
|
||||
sources: TournamentBracketProgression[number]["sources"];
|
||||
type: TournamentBracketProgression[number]["type"];
|
||||
|
|
@ -304,12 +293,7 @@ export class Tournament {
|
|||
new Map() as Map<number, number[]>,
|
||||
);
|
||||
|
||||
const bracketReplays = (
|
||||
candidateTeams: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[],
|
||||
) => {
|
||||
const bracketReplays = (candidateTeams: number[]) => {
|
||||
const manager = getTournamentManager();
|
||||
manager.create({
|
||||
tournamentId: this.ctx.id,
|
||||
|
|
@ -358,12 +342,12 @@ export class Tournament {
|
|||
const [oneId, twoId] = replays[0];
|
||||
|
||||
const lowerSeedId =
|
||||
newOrder.findIndex((t) => t.id === oneId) <
|
||||
newOrder.findIndex((t) => t.id === twoId)
|
||||
newOrder.findIndex((t) => t === oneId) <
|
||||
newOrder.findIndex((t) => t === twoId)
|
||||
? twoId
|
||||
: oneId;
|
||||
|
||||
if (!potentialSwitchCandidates.some((t) => t.id === lowerSeedId)) {
|
||||
if (!potentialSwitchCandidates.some((t) => t === lowerSeedId)) {
|
||||
logger.warn(
|
||||
`Avoiding replays failed, no potential switch candidates found in match: ${oneId} vs. ${twoId}`,
|
||||
);
|
||||
|
|
@ -373,10 +357,10 @@ export class Tournament {
|
|||
|
||||
for (const candidate of potentialSwitchCandidates) {
|
||||
// can't switch place with itself
|
||||
if (candidate.id === lowerSeedId) continue;
|
||||
if (candidate === lowerSeedId) continue;
|
||||
|
||||
const candidateIdx = newOrder.findIndex((t) => t.id === candidate.id);
|
||||
const otherIdx = newOrder.findIndex((t) => t.id === lowerSeedId);
|
||||
const candidateIdx = newOrder.findIndex((t) => t === candidate);
|
||||
const otherIdx = newOrder.findIndex((t) => t === lowerSeedId);
|
||||
|
||||
const temp = newOrder[candidateIdx];
|
||||
newOrder[candidateIdx] = newOrder[otherIdx];
|
||||
|
|
@ -403,12 +387,12 @@ export class Tournament {
|
|||
teams,
|
||||
bracketIdx,
|
||||
}: {
|
||||
teams: { id: number; name: string }[];
|
||||
teams: number[];
|
||||
bracketIdx: number;
|
||||
}) {
|
||||
return teams.reduce(
|
||||
(acc, cur) => {
|
||||
const team = this.teamById(cur.id);
|
||||
const team = this.teamById(cur);
|
||||
invariant(team, "Team not found");
|
||||
|
||||
const usesRegularCheckIn = bracketIdx === 0;
|
||||
|
|
@ -431,8 +415,8 @@ export class Tournament {
|
|||
return acc;
|
||||
},
|
||||
{ checkedInTeams: [], notCheckedInTeams: [] } as {
|
||||
checkedInTeams: { id: number; name: string }[];
|
||||
notCheckedInTeams: { id: number; name: string }[];
|
||||
checkedInTeams: number[];
|
||||
notCheckedInTeams: number[];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -887,9 +871,11 @@ export class Tournament {
|
|||
}
|
||||
|
||||
const participantInAnotherBracket = ongoingFollowUpBrackets
|
||||
.flatMap((b) => b.data.participant)
|
||||
.flatMap((b) => b.participantTournamentTeamIds)
|
||||
.some(
|
||||
(p) => p.id === match.opponent1?.id || p.id === match.opponent2?.id,
|
||||
(tournamentTeamId) =>
|
||||
tournamentTeamId === match.opponent1?.id ||
|
||||
tournamentTeamId === match.opponent2?.id,
|
||||
);
|
||||
|
||||
return participantInAnotherBracket;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// this file offers database functions specifically for the crud.server.ts file
|
||||
|
||||
import type {
|
||||
Participant,
|
||||
Stage as StageType,
|
||||
Group as GroupType,
|
||||
Round as RoundType,
|
||||
|
|
@ -9,42 +8,15 @@ import type {
|
|||
} from "~/modules/brackets-model";
|
||||
import { sql } from "~/db/sql";
|
||||
import type {
|
||||
Tournament,
|
||||
TournamentGroup,
|
||||
TournamentMatch,
|
||||
TournamentRound,
|
||||
TournamentStage,
|
||||
TournamentTeam,
|
||||
} from "~/db/types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import type { TournamentRoundMaps } from "~/db/tables";
|
||||
|
||||
const team_getByTournamentIdStm = sql.prepare(/*sql*/ `
|
||||
select
|
||||
*
|
||||
from
|
||||
"TournamentTeam"
|
||||
where
|
||||
"TournamentTeam"."tournamentId" = @tournamentId
|
||||
`);
|
||||
|
||||
export class Team {
|
||||
static #convertTeam(rawTeam: TournamentTeam): Participant {
|
||||
return {
|
||||
id: rawTeam.id,
|
||||
name: rawTeam.name,
|
||||
tournament_id: rawTeam.tournamentId,
|
||||
};
|
||||
}
|
||||
|
||||
static getByTournamentId(tournamentId: Tournament["id"]): Participant[] {
|
||||
return (team_getByTournamentIdStm.all({ tournamentId }) as any[]).map(
|
||||
this.#convertTeam,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stage_getByIdStm = sql.prepare(/*sql*/ `
|
||||
select
|
||||
*
|
||||
|
|
@ -137,7 +109,7 @@ export class Stage {
|
|||
return this.#convertStage(stage);
|
||||
}
|
||||
|
||||
static getByTournamentId(tournamentId: Tournament["id"]): Participant[] {
|
||||
static getByTournamentId(tournamentId: number): StageType[] {
|
||||
return (stage_getByTournamentIdStm.all({ tournamentId }) as any[]).map(
|
||||
this.#convertStage,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -142,28 +142,6 @@ export const FOUR_TEAMS_RR = (): ValueToArray<DataTypes> => ({
|
|||
},
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 0,
|
||||
tournament_id: 1,
|
||||
name: "Team 1",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
tournament_id: 1,
|
||||
name: "Team 2",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tournament_id: 1,
|
||||
name: "Team 3",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tournament_id: 1,
|
||||
name: "Team 4",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const FIVE_TEAMS_RR = (): ValueToArray<DataTypes> => ({
|
||||
|
|
@ -383,33 +361,6 @@ export const FIVE_TEAMS_RR = (): ValueToArray<DataTypes> => ({
|
|||
},
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 0,
|
||||
tournament_id: 3,
|
||||
name: "Team 1",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
tournament_id: 3,
|
||||
name: "Team 2",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tournament_id: 3,
|
||||
name: "Team 3",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tournament_id: 3,
|
||||
name: "Team 4",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
tournament_id: 3,
|
||||
name: "Team 5",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const SIX_TEAMS_TWO_GROUPS_RR = (): ValueToArray<DataTypes> => ({
|
||||
|
|
@ -576,38 +527,6 @@ export const SIX_TEAMS_TWO_GROUPS_RR = (): ValueToArray<DataTypes> => ({
|
|||
},
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 0,
|
||||
tournament_id: 3,
|
||||
name: "Team 1",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
tournament_id: 3,
|
||||
name: "Team 2",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tournament_id: 3,
|
||||
name: "Team 3",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tournament_id: 3,
|
||||
name: "Team 4",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
tournament_id: 3,
|
||||
name: "Team 5",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
tournament_id: 3,
|
||||
name: "Team 6",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const PADDLING_POOL_257 = () =>
|
||||
|
|
@ -2056,183 +1975,6 @@ export const PADDLING_POOL_257 = () =>
|
|||
lastGameFinishedAt: 1709752657,
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 819,
|
||||
name: "Heaven sent Lunatics",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 828,
|
||||
name: "4Ever",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 838,
|
||||
name: "G Gaming Gaming",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 839,
|
||||
name: "1HP",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 843,
|
||||
name: "Rogueport Rascals",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 845,
|
||||
name: "Smoking Moais ",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 847,
|
||||
name: "Big Tommy and the Flops",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 867,
|
||||
name: "NEVER BACK DOWN NEVER WHAT?",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 869,
|
||||
name: "Müll🚮",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 870,
|
||||
name: "Shade",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 871,
|
||||
name: "ASC Niji",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 872,
|
||||
name: "Intrusive thoughts ",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 873,
|
||||
name: "Préférence Pêche ",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 874,
|
||||
name: "Squid Emoji",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 875,
|
||||
name: "Chippeur arrête de Chipper",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 878,
|
||||
name: "SAN DIMAS HS FOOTBALL RULES!",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 879,
|
||||
name: "ASC Shokkai ",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 881,
|
||||
name: "New Generation",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 882,
|
||||
name: "Chaos Control",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 883,
|
||||
name: "Second Try",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 884,
|
||||
name: "Hazard",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 886,
|
||||
name: "Seaya",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 887,
|
||||
name: "AquaSonix",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 888,
|
||||
name: "Naw, I’d win",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 889,
|
||||
name: "DistInkt",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 890,
|
||||
name: "better gaming chair",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 891,
|
||||
name: "Mafia mbappe",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 892,
|
||||
name: "Le classique à Cam",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 893,
|
||||
name: "Ink Souls Maria",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 896,
|
||||
name: "<_>Placeholder",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 897,
|
||||
name: "Splash Mirrors 3",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 898,
|
||||
name: "Atsub",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 900,
|
||||
name: "There’s a snake in my boot 🐍",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 901,
|
||||
name: "Pickup oder so ig",
|
||||
tournament_id: 27,
|
||||
},
|
||||
{
|
||||
id: 902,
|
||||
name: "Blaze",
|
||||
tournament_id: 27,
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx: {
|
||||
id: 27,
|
||||
|
|
@ -7940,183 +7682,6 @@ export const PADDLING_POOL_255 = () =>
|
|||
lastGameFinishedAt: 1708541417,
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 672,
|
||||
name: "Müll🚮",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 673,
|
||||
name: "Grandma Sicko Mode",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 674,
|
||||
name: "Smoking Moais ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 675,
|
||||
name: "Yoghurt Party",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 676,
|
||||
name: "NEVER BACK DOWN NEVER WHAT",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 677,
|
||||
name: "4Ever",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 678,
|
||||
name: "Amoura",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 695,
|
||||
name: "Hisense RL170D4BWE Freestanding ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 697,
|
||||
name: "Atlass",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 698,
|
||||
name: "Please don't forfeit. ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 699,
|
||||
name: "Stream easy by lesserafim",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 700,
|
||||
name: "Fresh takos ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 701,
|
||||
name: "Hazard",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 702,
|
||||
name: "ici ça bzzz",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 704,
|
||||
name: "Second Try",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 705,
|
||||
name: "Flutter Mane Fanclub",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 706,
|
||||
name: "DistInkt",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 707,
|
||||
name: "Chaos Control",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 708,
|
||||
name: "91 c'est la Champions League",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 709,
|
||||
name: "ASC Shokkai ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 710,
|
||||
name: "Bloody Wave",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 712,
|
||||
name: "Revenge ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 713,
|
||||
name: "https://youtu.be/Euq7uTeYCP0?si=",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 714,
|
||||
name: "youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 715,
|
||||
name: "Rule them",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 716,
|
||||
name: "allo kayora ?",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 717,
|
||||
name: "ASC Niji~K",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 718,
|
||||
name: "Enperries 200p",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 719,
|
||||
name: "Gambawaffeln",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 720,
|
||||
name: "Blaze",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 721,
|
||||
name: "Squid Emoji",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 722,
|
||||
name: "iPad jaune",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 723,
|
||||
name: "We Are Innocent Caterpillars",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 724,
|
||||
name: "menoks!",
|
||||
tournament_id: 18,
|
||||
},
|
||||
{
|
||||
id: 725,
|
||||
name: "Ink Souls Maria",
|
||||
tournament_id: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx: {
|
||||
id: 18,
|
||||
|
|
@ -14162,178 +13727,6 @@ export const IN_THE_ZONE_32 = () =>
|
|||
lastGameFinishedAt: null,
|
||||
},
|
||||
],
|
||||
participant: [
|
||||
{
|
||||
id: 499,
|
||||
name: "Grougrou ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 500,
|
||||
name: "Hades",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 507,
|
||||
name: "Celeste",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 509,
|
||||
name: "New Generation ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 546,
|
||||
name: "Moonlight",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 547,
|
||||
name: "atomic bomb ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 548,
|
||||
name: "Jackpot",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 551,
|
||||
name: "Zoneando",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 552,
|
||||
name: "Starburst",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 560,
|
||||
name: "Yaotl Teotl",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 564,
|
||||
name: "Black Lotus ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 589,
|
||||
name: "11:11; Make a Wish",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 590,
|
||||
name: "ASC Tenshi",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 591,
|
||||
name: "BEt",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 592,
|
||||
name: "As you wish",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 593,
|
||||
name: "Whaa",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 594,
|
||||
name: "FOAMS 34",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 595,
|
||||
name: "Hypernova",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 596,
|
||||
name: "metal pipe",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 597,
|
||||
name: "OVERTIME!!",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 598,
|
||||
name: "Gen BOB Ten-Piece Chicken Nugget",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 599,
|
||||
name: "Hazard",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 600,
|
||||
name: "Gentlemates",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 601,
|
||||
name: "Zenith",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 603,
|
||||
name: "JumpingCatapult ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 604,
|
||||
name: "Joga Bonito",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 605,
|
||||
name: "all my homie hate pencil ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 606,
|
||||
name: "Peaky P-Key ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 608,
|
||||
name: "Hiro le tournevis",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 609,
|
||||
name: "UK MAFIA",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 610,
|
||||
name: "Smoking Moais ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 611,
|
||||
name: "Replay",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 612,
|
||||
name: "delulu ",
|
||||
tournament_id: 11,
|
||||
},
|
||||
{
|
||||
id: 614,
|
||||
name: "Reputation (Taylor's Version)",
|
||||
tournament_id: 11,
|
||||
},
|
||||
],
|
||||
},
|
||||
ctx: {
|
||||
id: 11,
|
||||
|
|
|
|||
|
|
@ -1,289 +0,0 @@
|
|||
import { suite } from "uvu";
|
||||
import { FIVE_TEAMS_RR, FOUR_TEAMS_RR, SIX_TEAMS_TWO_GROUPS_RR } from "./mocks";
|
||||
import { adjustResults, testTournament } from "./test-utils";
|
||||
import * as assert from "uvu/assert";
|
||||
import { BRACKET_NAMES } from "~/features/tournament/tournament-constants";
|
||||
import type { TournamentData } from "../Tournament.server";
|
||||
|
||||
const RoundRobinStandings = suite("Round Robin Standings");
|
||||
|
||||
const roundRobinTournamentCtx: Partial<TournamentData["ctx"]> = {
|
||||
settings: {
|
||||
bracketProgression: [{ name: BRACKET_NAMES.GROUPS, type: "round_robin" }],
|
||||
},
|
||||
inProgressBrackets: [
|
||||
{ id: 0, type: "round_robin", name: BRACKET_NAMES.GROUPS, createdAt: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
RoundRobinStandings("resolves standings from points", () => {
|
||||
const tournament = testTournament(
|
||||
adjustResults(FOUR_TEAMS_RR(), [
|
||||
{ ids: [0, 3], score: [2, 0] },
|
||||
{ ids: [2, 1], score: [0, 2] },
|
||||
{ ids: [1, 3], score: [2, 0] },
|
||||
{ ids: [0, 2], score: [2, 0] },
|
||||
{ ids: [2, 3], score: [2, 0] },
|
||||
{ ids: [1, 0], score: [0, 2] },
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings.length, 4);
|
||||
assert.equal(standings[0].team.id, 0);
|
||||
assert.equal(standings[0].placement, 1);
|
||||
assert.equal(standings[1].team.id, 1);
|
||||
assert.equal(standings[2].team.id, 2);
|
||||
assert.equal(standings[3].team.id, 3);
|
||||
});
|
||||
|
||||
RoundRobinStandings("tiebreaker via head-to-head", () => {
|
||||
// id 0 = WWWL
|
||||
// id 1 = WWWL
|
||||
// id 2 = WWLL
|
||||
// id 3 = WWLL but won against 2
|
||||
// id 4 = LLLL
|
||||
const tournament = testTournament(
|
||||
adjustResults(FIVE_TEAMS_RR(), [
|
||||
{
|
||||
ids: [4, 1],
|
||||
score: [0, 2],
|
||||
},
|
||||
{
|
||||
ids: [0, 2],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [4, 3],
|
||||
score: [0, 2],
|
||||
},
|
||||
{
|
||||
ids: [1, 3],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [0, 4],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [2, 4],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [1, 0],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [3, 0],
|
||||
score: [0, 2],
|
||||
},
|
||||
{
|
||||
ids: [2, 1],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [3, 2],
|
||||
score: [2, 0],
|
||||
},
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings.length, 5);
|
||||
assert.equal(standings[2].team.id, 3);
|
||||
assert.equal(standings[2].placement, 3);
|
||||
assert.equal(standings[3].team.id, 2);
|
||||
assert.equal(standings[3].placement, 4);
|
||||
});
|
||||
|
||||
RoundRobinStandings("tiebreaker via maps won", () => {
|
||||
// id 0 = WWWW
|
||||
// id 1 = WWLL
|
||||
// id 2 = WWLL
|
||||
// id 3 = WWLL
|
||||
const tournament = testTournament(
|
||||
adjustResults(FOUR_TEAMS_RR(), [
|
||||
{ ids: [0, 3], score: [2, 0] },
|
||||
{ ids: [2, 1], score: [0, 2] },
|
||||
{ ids: [1, 3], score: [0, 2] },
|
||||
{ ids: [0, 2], score: [2, 0] },
|
||||
{ ids: [2, 3], score: [2, 1] },
|
||||
{ ids: [1, 0], score: [0, 2] },
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
// they won the most maps out of the 3 tied teams
|
||||
assert.equal(standings[1].team.id, 3);
|
||||
});
|
||||
|
||||
RoundRobinStandings("three way tiebreaker via points scored", () => {
|
||||
// id 0 = LLL
|
||||
// id 1 = WWL
|
||||
// id 2 = WWL
|
||||
// id 3 = WWL
|
||||
const tournament = testTournament(
|
||||
adjustResults(FOUR_TEAMS_RR(), [
|
||||
{ ids: [0, 3], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [2, 1], score: [0, 2], points: [50, 100] },
|
||||
{ ids: [1, 3], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [0, 2], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [2, 3], score: [2, 0], points: [150, 149] },
|
||||
{ ids: [1, 0], score: [2, 0], points: [200, 0] },
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings[0].team.id, 3);
|
||||
assert.equal(standings[1].team.id, 2);
|
||||
});
|
||||
|
||||
RoundRobinStandings("if everything is tied, uses seeds as tiebreaker", () => {
|
||||
// id 0 = LLL
|
||||
// id 1 = WWL
|
||||
// id 2 = WWL
|
||||
// id 3 = WWL
|
||||
const tournament = testTournament(
|
||||
adjustResults(FOUR_TEAMS_RR(), [
|
||||
{ ids: [0, 3], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [2, 1], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [1, 3], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [0, 2], score: [0, 2], points: [0, 200] },
|
||||
{ ids: [2, 3], score: [2, 0], points: [200, 0] },
|
||||
{ ids: [1, 0], score: [2, 0], points: [200, 0] },
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings[0].team.id, 1);
|
||||
assert.equal(standings[1].team.id, 2);
|
||||
});
|
||||
|
||||
RoundRobinStandings("if two groups finished, standings for both groups", () => {
|
||||
const tournament = testTournament(
|
||||
adjustResults(SIX_TEAMS_TWO_GROUPS_RR(), [
|
||||
{
|
||||
ids: [4, 3],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [0, 4],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [3, 0],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [5, 2],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [1, 5],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [2, 1],
|
||||
score: [2, 0],
|
||||
},
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings.length, 6);
|
||||
assert.equal(standings.filter((s) => s.placement === 1).length, 2);
|
||||
});
|
||||
|
||||
RoundRobinStandings(
|
||||
"if one group finished and other ongoing, standings for just one group",
|
||||
() => {
|
||||
const tournament = testTournament(
|
||||
adjustResults(SIX_TEAMS_TWO_GROUPS_RR(), [
|
||||
{
|
||||
ids: [4, 3],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [0, 4],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [3, 0],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [5, 2],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [1, 5],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [2, 1],
|
||||
score: [0, 0],
|
||||
},
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings.length, 3);
|
||||
assert.equal(standings.filter((s) => s.placement === 1).length, 1);
|
||||
},
|
||||
);
|
||||
|
||||
RoundRobinStandings(
|
||||
"teams with same placements are ordered by group id",
|
||||
() => {
|
||||
const base = SIX_TEAMS_TWO_GROUPS_RR();
|
||||
const tournament = testTournament(
|
||||
adjustResults({ ...base, group: base.group.reverse() }, [
|
||||
{
|
||||
ids: [4, 3],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [0, 4],
|
||||
score: [0, 2],
|
||||
},
|
||||
{
|
||||
ids: [3, 0],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [5, 2],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [1, 5],
|
||||
score: [2, 0],
|
||||
},
|
||||
{
|
||||
ids: [2, 1],
|
||||
score: [2, 0],
|
||||
},
|
||||
]),
|
||||
roundRobinTournamentCtx,
|
||||
);
|
||||
|
||||
const standings = tournament.bracketByIdx(0)!.standings;
|
||||
|
||||
assert.equal(standings[0].team.id, 4);
|
||||
},
|
||||
);
|
||||
|
||||
RoundRobinStandings.run();
|
||||
|
|
@ -2,6 +2,7 @@ import { BRACKET_NAMES } from "~/features/tournament/tournament-constants";
|
|||
import { Tournament } from "../Tournament";
|
||||
import type { TournamentData } from "../Tournament.server";
|
||||
import type { DataTypes, ValueToArray } from "~/modules/brackets-manager/types";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
|
||||
const tournamentCtxTeam = (
|
||||
teamId: number,
|
||||
|
|
@ -37,6 +38,12 @@ export const testTournament = (
|
|||
data: ValueToArray<DataTypes>,
|
||||
partialCtx?: Partial<TournamentData["ctx"]>,
|
||||
) => {
|
||||
const participant = removeDuplicates(
|
||||
data.match
|
||||
.flatMap((m) => [m.opponent1?.id, m.opponent2?.id])
|
||||
.filter(Boolean),
|
||||
) as number[];
|
||||
|
||||
return new Tournament({
|
||||
data,
|
||||
ctx: {
|
||||
|
|
@ -68,10 +75,7 @@ export const testTournament = (
|
|||
createdAt: 0,
|
||||
})),
|
||||
castedMatchesInfo: null,
|
||||
teams: nTeams(
|
||||
data.participant.length,
|
||||
Math.min(...data.participant.map((p) => p.id)),
|
||||
),
|
||||
teams: nTeams(participant.length, Math.min(...participant)),
|
||||
author: {
|
||||
chatNameColor: null,
|
||||
customUrl: null,
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ export default function TournamentBracketsPage() {
|
|||
bracketIdx === 0
|
||||
? tournament.ctx.teams.length
|
||||
: (bracket.teamsPendingCheckIn ?? []).length +
|
||||
bracket.data.participant.length;
|
||||
bracket.participantTournamentTeamIds.length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -401,7 +401,7 @@ export default function TournamentBracketsPage() {
|
|||
alertClassName="tournament-bracket__start-bracket-alert"
|
||||
textClassName="stack horizontal md items-center"
|
||||
>
|
||||
{bracket.data.participant.length}/
|
||||
{bracket.participantTournamentTeamIds.length}/
|
||||
{totalTeamsAvailableForTheBracket()} teams checked in
|
||||
{bracket.canBeStarted ? (
|
||||
<BracketStarter bracket={bracket} bracketIdx={bracketIdx} />
|
||||
|
|
@ -506,7 +506,11 @@ function MiniCheckinInfoBanner({
|
|||
|
||||
if (!teamMemberOf) return null;
|
||||
|
||||
if (bracket.data.participant.some((p) => p.id === teamMemberOf.id)) {
|
||||
if (
|
||||
bracket.participantTournamentTeamIds.some(
|
||||
(tournamentTeamId) => tournamentTeamId === teamMemberOf.id,
|
||||
)
|
||||
) {
|
||||
return (
|
||||
<div className="tournament-bracket__mini-alert">
|
||||
✅ Your team is checked in to the bracket (ask the TO for a check-out if
|
||||
|
|
|
|||
|
|
@ -1,107 +1,11 @@
|
|||
import type {
|
||||
Match,
|
||||
Seeding,
|
||||
Stage,
|
||||
GroupType,
|
||||
} from "~/modules/brackets-model";
|
||||
import type { GroupType, Match, Stage } from "~/modules/brackets-model";
|
||||
import { Status } from "~/modules/brackets-model";
|
||||
import type { DeepPartial, ParticipantSlot, Side } from "../types";
|
||||
import type { SetNextOpponent } from "../helpers";
|
||||
import { ordering } from "../ordering";
|
||||
import { Create } from "../create";
|
||||
import { BaseGetter } from "./getter";
|
||||
import { Get } from "../get";
|
||||
import * as helpers from "../helpers";
|
||||
import type { DeepPartial, Side } from "../types";
|
||||
import { BaseGetter } from "./getter";
|
||||
|
||||
export class BaseUpdater extends BaseGetter {
|
||||
/**
|
||||
* Updates or resets the seeding of a stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
* @param seeding A new seeding or `null` to reset the existing seeding.
|
||||
*/
|
||||
protected updateSeeding(stageId: number, seeding: Seeding | null): void {
|
||||
const stage = this.storage.select("stage", stageId);
|
||||
if (!stage) throw Error("Stage not found.");
|
||||
|
||||
const create = new Create(this.storage, {
|
||||
name: stage.name,
|
||||
tournamentId: stage.tournament_id,
|
||||
type: stage.type,
|
||||
settings: stage.settings,
|
||||
seeding: seeding || undefined,
|
||||
});
|
||||
|
||||
create.setExisting(stageId, false);
|
||||
|
||||
const method = BaseGetter.getSeedingOrdering(stage.type, create);
|
||||
const slots = create.getSlots();
|
||||
|
||||
const matches = this.getSeedingMatches(stage.id, stage.type);
|
||||
if (!matches)
|
||||
throw Error("Error getting matches associated to the seeding.");
|
||||
|
||||
const ordered = ordering[method](slots);
|
||||
BaseUpdater.assertCanUpdateSeeding(matches, ordered);
|
||||
|
||||
create.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the current seeding of a stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
protected confirmCurrentSeeding(stageId: number): void {
|
||||
const stage = this.storage.select("stage", stageId);
|
||||
if (!stage) throw Error("Stage not found.");
|
||||
|
||||
const get = new Get(this.storage);
|
||||
const currentSeeding = get.seeding(stageId);
|
||||
const newSeeding = helpers.convertSlotsToSeeding(
|
||||
currentSeeding.map(helpers.convertTBDtoBYE),
|
||||
);
|
||||
|
||||
const create = new Create(this.storage, {
|
||||
name: stage.name,
|
||||
tournamentId: stage.tournament_id,
|
||||
type: stage.type,
|
||||
settings: stage.settings,
|
||||
seeding: newSeeding,
|
||||
});
|
||||
|
||||
create.setExisting(stageId, true);
|
||||
|
||||
create.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if a match is locked and the new seeding will change this match's participants.
|
||||
*
|
||||
* @param matches The matches stored in the database.
|
||||
* @param slots The slots to check from the new seeding.
|
||||
*/
|
||||
protected static assertCanUpdateSeeding(
|
||||
matches: Match[],
|
||||
slots: ParticipantSlot[],
|
||||
): void {
|
||||
let index = 0;
|
||||
|
||||
for (const match of matches) {
|
||||
const opponent1 = slots[index++];
|
||||
const opponent2 = slots[index++];
|
||||
|
||||
const locked = helpers.isMatchParticipantLocked(match);
|
||||
if (!locked) continue;
|
||||
|
||||
if (
|
||||
match.opponent1?.id !== opponent1?.id ||
|
||||
match.opponent2?.id !== opponent2?.id
|
||||
)
|
||||
throw Error("A match is locked.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the matches related (previous and next) to a match.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ import type {
|
|||
Group,
|
||||
InputStage,
|
||||
Match,
|
||||
Participant,
|
||||
Round,
|
||||
Seeding,
|
||||
SeedOrdering,
|
||||
Seeding,
|
||||
Stage,
|
||||
} from "~/modules/brackets-model";
|
||||
import { defaultMinorOrdering, ordering } from "./ordering";
|
||||
|
|
@ -569,37 +568,7 @@ export class Create {
|
|||
this.stage.settings.size,
|
||||
);
|
||||
|
||||
if (helpers.isSeedingWithIds(this.stage.seeding))
|
||||
return this.getSlotsUsingIds(this.stage.seeding, positions);
|
||||
|
||||
return this.getSlotsUsingNames(this.stage.seeding, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of slots with a seeding containing names. Participants may be added to database.
|
||||
*
|
||||
* @param seeding The seeding (names).
|
||||
* @param positions An optional list of positions (seeds) for a manual ordering.
|
||||
*/
|
||||
private getSlotsUsingNames(
|
||||
seeding: Seeding,
|
||||
positions?: number[],
|
||||
): ParticipantSlot[] {
|
||||
const participants = helpers.extractParticipantsFromSeeding(
|
||||
this.stage.tournamentId,
|
||||
seeding,
|
||||
);
|
||||
|
||||
if (!this.registerParticipants(participants))
|
||||
throw Error("Error registering the participants.");
|
||||
|
||||
// Get participants back with IDs.
|
||||
const added = this.storage.select("participant", {
|
||||
tournament_id: this.stage.tournamentId,
|
||||
});
|
||||
if (!added) throw Error("Error getting registered participant.");
|
||||
|
||||
return helpers.mapParticipantsNamesToDatabase(seeding, added, positions);
|
||||
return this.getSlotsUsingIds(this.stage.seeding, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -612,16 +581,21 @@ export class Create {
|
|||
seeding: Seeding,
|
||||
positions?: number[],
|
||||
): ParticipantSlot[] {
|
||||
const participants = this.storage.select("participant", {
|
||||
tournament_id: this.stage.tournamentId,
|
||||
});
|
||||
if (!participants) throw Error("No available participants.");
|
||||
if (positions && positions.length !== seeding.length) {
|
||||
throw Error(
|
||||
"Not enough seeds in at least one group of the manual ordering.",
|
||||
);
|
||||
}
|
||||
|
||||
return helpers.mapParticipantsIdsToDatabase(
|
||||
seeding,
|
||||
participants,
|
||||
positions,
|
||||
);
|
||||
const slots = seeding.map((slot, i) => {
|
||||
if (slot === null) return null; // BYE.
|
||||
|
||||
return { id: slot, position: i + 1 };
|
||||
});
|
||||
|
||||
if (!positions) return slots;
|
||||
|
||||
return positions.map((position) => slots[position - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -851,31 +825,6 @@ export class Create {
|
|||
return existing.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts missing participants.
|
||||
*
|
||||
* @param participants The list of participants to process.
|
||||
*/
|
||||
private registerParticipants(participants: OmitId<Participant>[]): boolean {
|
||||
const existing = this.storage.select("participant", {
|
||||
tournament_id: this.stage.tournamentId,
|
||||
});
|
||||
|
||||
// Insert all if nothing.
|
||||
if (!existing || existing.length === 0)
|
||||
return this.storage.insert("participant", participants);
|
||||
|
||||
// Insert only missing otherwise.
|
||||
for (const participant of participants) {
|
||||
if (existing.some((value) => value.name === participant.name)) continue;
|
||||
|
||||
const result = this.storage.insert("participant", participant);
|
||||
if (result === -1) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stage.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
import type {
|
||||
Stage,
|
||||
Group,
|
||||
Round,
|
||||
Match,
|
||||
Participant,
|
||||
} from "~/modules/brackets-model";
|
||||
import type { Stage, Group, Round, Match } from "~/modules/brackets-model";
|
||||
import { Status } from "~/modules/brackets-model";
|
||||
import type { Database, FinalStandingsItem, ParticipantSlot } from "./types";
|
||||
import type { Database, ParticipantSlot } from "./types";
|
||||
import { BaseGetter } from "./base/getter";
|
||||
import * as helpers from "./helpers";
|
||||
|
||||
|
|
@ -19,17 +13,11 @@ export class Get extends BaseGetter {
|
|||
public stageData(stageId: number): Database {
|
||||
const stageData = this.getStageSpecificData(stageId);
|
||||
|
||||
const participants = this.storage.select("participant", {
|
||||
tournament_id: stageData.stage.tournament_id,
|
||||
});
|
||||
if (!participants) throw Error("Error getting participants.");
|
||||
|
||||
return {
|
||||
stage: [stageData.stage],
|
||||
group: stageData.groups,
|
||||
round: stageData.rounds,
|
||||
match: stageData.matches,
|
||||
participant: participants,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +36,6 @@ export class Get extends BaseGetter {
|
|||
this.getStageSpecificData(stage.id),
|
||||
);
|
||||
|
||||
const participants = this.storage.select("participant", {
|
||||
tournament_id: tournamentId,
|
||||
});
|
||||
if (!participants) throw Error("Error getting participants.");
|
||||
|
||||
return {
|
||||
stage: stages,
|
||||
group: stagesData.reduce(
|
||||
|
|
@ -67,7 +50,6 @@ export class Get extends BaseGetter {
|
|||
(acc, data) => [...acc, ...data.matches],
|
||||
[] as Match[],
|
||||
),
|
||||
participant: participants,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -214,30 +196,6 @@ export class Get extends BaseGetter {
|
|||
return this.eliminationSeeding(stage).map(pickRelevantProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final standings of a stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
public finalStandings(stageId: number): FinalStandingsItem[] {
|
||||
const stage = this.storage.select("stage", stageId);
|
||||
if (!stage) throw Error("Stage not found.");
|
||||
|
||||
switch (stage.type) {
|
||||
case "round_robin":
|
||||
throw Error("A round-robin stage does not have standings.");
|
||||
case "single_elimination":
|
||||
return this.singleEliminationStandings(stageId);
|
||||
case "double_elimination":
|
||||
if (stage.settings.size === 2) {
|
||||
return this.singleEliminationStandings(stageId);
|
||||
}
|
||||
return this.doubleEliminationStandings(stageId);
|
||||
default:
|
||||
throw Error("Unknown stage type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the seeding of a round-robin stage.
|
||||
*
|
||||
|
|
@ -282,134 +240,6 @@ export class Get extends BaseGetter {
|
|||
return helpers.convertMatchesToSeeding(matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final standings of a single elimination stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
private singleEliminationStandings(stageId: number): FinalStandingsItem[] {
|
||||
const grouped: Participant[][] = [];
|
||||
|
||||
const {
|
||||
stage: stages,
|
||||
group: groups,
|
||||
match: matches,
|
||||
participant: participants,
|
||||
} = this.stageData(stageId);
|
||||
|
||||
const [stage] = stages;
|
||||
const [singleBracket, finalGroup] = groups;
|
||||
|
||||
const final = matches
|
||||
.filter((match) => match.group_id === singleBracket.id)
|
||||
.pop();
|
||||
if (!final) throw Error("Final not found.");
|
||||
|
||||
// 1st place: Final winner.
|
||||
grouped[0] = [
|
||||
helpers.findParticipant(participants, getFinalWinnerIfDefined(final)),
|
||||
];
|
||||
|
||||
// Rest: every loser in reverse order.
|
||||
const losers = helpers.getLosers(
|
||||
participants,
|
||||
matches.filter((match) => match.group_id === singleBracket.id),
|
||||
);
|
||||
grouped.push(...losers.reverse());
|
||||
|
||||
if (stage.settings?.consolationFinal) {
|
||||
const consolationFinal = matches
|
||||
.filter((match) => match.group_id === finalGroup.id)
|
||||
.pop();
|
||||
if (!consolationFinal) throw Error("Consolation final not found.");
|
||||
|
||||
const consolationFinalWinner = helpers.findParticipant(
|
||||
participants,
|
||||
getFinalWinnerIfDefined(consolationFinal),
|
||||
);
|
||||
const consolationFinalLoser = helpers.findParticipant(
|
||||
participants,
|
||||
helpers.getLoser(consolationFinal),
|
||||
);
|
||||
|
||||
// Overwrite semi-final losers with the consolation final results.
|
||||
grouped.splice(2, 1, [consolationFinalWinner], [consolationFinalLoser]);
|
||||
}
|
||||
|
||||
return helpers.makeFinalStandings(grouped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final standings of a double elimination stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
private doubleEliminationStandings(stageId: number): FinalStandingsItem[] {
|
||||
const grouped: Participant[][] = [];
|
||||
|
||||
const {
|
||||
stage: stages,
|
||||
group: groups,
|
||||
match: matches,
|
||||
participant: participants,
|
||||
} = this.stageData(stageId);
|
||||
|
||||
const [stage] = stages;
|
||||
const [winnerBracket, loserBracket, finalGroup] = groups;
|
||||
|
||||
if (stage.settings?.grandFinal === "none") {
|
||||
const finalWB = matches
|
||||
.filter((match) => match.group_id === winnerBracket.id)
|
||||
.pop();
|
||||
if (!finalWB) throw Error("WB final not found.");
|
||||
|
||||
const finalLB = matches
|
||||
.filter((match) => match.group_id === loserBracket.id)
|
||||
.pop();
|
||||
if (!finalLB) throw Error("LB final not found.");
|
||||
|
||||
// 1st place: WB Final winner.
|
||||
grouped[0] = [
|
||||
helpers.findParticipant(participants, getFinalWinnerIfDefined(finalWB)),
|
||||
];
|
||||
|
||||
// 2nd place: LB Final winner.
|
||||
grouped[1] = [
|
||||
helpers.findParticipant(participants, getFinalWinnerIfDefined(finalLB)),
|
||||
];
|
||||
} else {
|
||||
const grandFinalMatches = matches.filter(
|
||||
(match) => match.group_id === finalGroup.id,
|
||||
);
|
||||
const decisiveMatch = helpers.getGrandFinalDecisiveMatch(
|
||||
stage.settings?.grandFinal || "none",
|
||||
grandFinalMatches,
|
||||
);
|
||||
|
||||
// 1st place: Grand Final winner.
|
||||
grouped[0] = [
|
||||
helpers.findParticipant(
|
||||
participants,
|
||||
getFinalWinnerIfDefined(decisiveMatch),
|
||||
),
|
||||
];
|
||||
|
||||
// 2nd place: Grand Final loser.
|
||||
grouped[1] = [
|
||||
helpers.findParticipant(participants, helpers.getLoser(decisiveMatch)),
|
||||
];
|
||||
}
|
||||
|
||||
// Rest: every loser in reverse order.
|
||||
const losers = helpers.getLosers(
|
||||
participants,
|
||||
matches.filter((match) => match.group_id === loserBracket.id),
|
||||
);
|
||||
grouped.push(...losers.reverse());
|
||||
|
||||
return helpers.makeFinalStandings(grouped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the data specific to the given stage (without the participants).
|
||||
*
|
||||
|
|
@ -441,9 +271,3 @@ export class Get extends BaseGetter {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getFinalWinnerIfDefined = (match: Match): ParticipantSlot => {
|
||||
const winner = helpers.getWinner(match);
|
||||
if (!winner) throw Error("The final match does not have a winner.");
|
||||
return winner;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import type {
|
|||
GrandFinalType,
|
||||
Match,
|
||||
MatchResults,
|
||||
Participant,
|
||||
ParticipantResult,
|
||||
CustomParticipant,
|
||||
Result,
|
||||
RoundRobinMode,
|
||||
Seeding,
|
||||
|
|
@ -19,16 +17,15 @@ import type {
|
|||
Database,
|
||||
DeepPartial,
|
||||
Duel,
|
||||
FinalStandingsItem,
|
||||
IdMapping,
|
||||
Nullable,
|
||||
OmitId,
|
||||
ParitySplit,
|
||||
ParticipantSlot,
|
||||
Scores,
|
||||
Side,
|
||||
} from "./types";
|
||||
import { ordering } from "./ordering";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
||||
/**
|
||||
* Splits an array of objects based on their values at a given key.
|
||||
|
|
@ -234,7 +231,6 @@ export function balanceByes(
|
|||
*/
|
||||
export function normalizeIds(data: Database): Database {
|
||||
const mappings = {
|
||||
participant: makeNormalizedIdMapping(data.participant),
|
||||
stage: makeNormalizedIdMapping(data.stage),
|
||||
group: makeNormalizedIdMapping(data.group),
|
||||
round: makeNormalizedIdMapping(data.round),
|
||||
|
|
@ -242,10 +238,6 @@ export function normalizeIds(data: Database): Database {
|
|||
};
|
||||
|
||||
return {
|
||||
participant: data.participant.map((value) => ({
|
||||
...value,
|
||||
id: mappings.participant[value.id],
|
||||
})),
|
||||
stage: data.stage.map((value) => ({
|
||||
...value,
|
||||
id: mappings.stage[value.id],
|
||||
|
|
@ -267,8 +259,8 @@ export function normalizeIds(data: Database): Database {
|
|||
stage_id: mappings.stage[value.stage_id],
|
||||
group_id: mappings.group[value.group_id],
|
||||
round_id: mappings.round[value.round_id],
|
||||
opponent1: normalizeParticipant(value.opponent1, mappings.participant),
|
||||
opponent2: normalizeParticipant(value.opponent2, mappings.participant),
|
||||
opponent1: value.opponent1,
|
||||
opponent2: value.opponent2,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
@ -912,11 +904,8 @@ export function getOriginPosition(match: Match, side: Side): number {
|
|||
* @param participants The list of participants.
|
||||
* @param matches A list of matches to get losers of.
|
||||
*/
|
||||
export function getLosers(
|
||||
participants: Participant[],
|
||||
matches: Match[],
|
||||
): Participant[][] {
|
||||
const losers: Participant[][] = [];
|
||||
export function getLosers(matches: Match[]): number[][] {
|
||||
const losers: number[][] = [];
|
||||
|
||||
let currentRound: number | null = null;
|
||||
let roundIndex = -1;
|
||||
|
|
@ -931,38 +920,13 @@ export function getLosers(
|
|||
const loser = getLoser(match);
|
||||
if (loser === null) continue;
|
||||
|
||||
losers[roundIndex].push(findParticipant(participants, loser));
|
||||
invariant(loser.id, "Loser id not found");
|
||||
losers[roundIndex].push(loser.id);
|
||||
}
|
||||
|
||||
return losers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes final standings based on participants grouped by ranking.
|
||||
*
|
||||
* @param grouped A list of participants grouped by ranking.
|
||||
*/
|
||||
export function makeFinalStandings(
|
||||
grouped: Participant[][],
|
||||
): FinalStandingsItem[] {
|
||||
const standings: FinalStandingsItem[] = [];
|
||||
|
||||
let rank = 1;
|
||||
|
||||
for (const group of grouped) {
|
||||
for (const participant of group) {
|
||||
standings.push({
|
||||
id: participant.id,
|
||||
name: participant.name,
|
||||
rank,
|
||||
});
|
||||
}
|
||||
rank++;
|
||||
}
|
||||
|
||||
return standings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decisive match of a Grand Final.
|
||||
*
|
||||
|
|
@ -986,24 +950,6 @@ export function getGrandFinalDecisiveMatch(
|
|||
throw Error("The Grand Final is disabled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a participant in a list.
|
||||
*
|
||||
* @param participants The list of participants.
|
||||
* @param slot The slot of the participant to find.
|
||||
*/
|
||||
export function findParticipant(
|
||||
participants: Participant[],
|
||||
slot: ParticipantSlot,
|
||||
): Participant {
|
||||
if (!slot) throw Error("Cannot find a BYE participant.");
|
||||
const participant = participants.find(
|
||||
(participant) => participant.id === slot?.id,
|
||||
);
|
||||
if (!participant) throw Error("Participant not found.");
|
||||
return participant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the side the winner of the current match will go to in the next match.
|
||||
*
|
||||
|
|
@ -1226,115 +1172,6 @@ export function setResults(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a seeding is filled with participants' IDs.
|
||||
*
|
||||
* @param seeding The seeding.
|
||||
*/
|
||||
export function isSeedingWithIds(seeding: Seeding): boolean {
|
||||
return seeding.some((value) => typeof value === "number");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts participants from a seeding, without the BYEs.
|
||||
*
|
||||
* @param tournamentId ID of the tournament.
|
||||
* @param seeding The seeding (no IDs).
|
||||
*/
|
||||
export function extractParticipantsFromSeeding(
|
||||
tournamentId: number,
|
||||
seeding: Seeding,
|
||||
): OmitId<Participant>[] {
|
||||
const withoutByes = seeding.filter(
|
||||
(name): name is /* number */ string | CustomParticipant => name !== null,
|
||||
);
|
||||
|
||||
const participants = withoutByes.map<OmitId<Participant>>((item) => {
|
||||
if (typeof item === "string") {
|
||||
return {
|
||||
tournament_id: tournamentId,
|
||||
name: item,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
tournament_id: tournamentId,
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
|
||||
return participants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns participant slots mapped to the instances stored in the database thanks to their name.
|
||||
*
|
||||
* @param seeding The seeding.
|
||||
* @param database The participants stored in the database.
|
||||
* @param positions An optional list of positions (seeds) for a manual ordering.
|
||||
*/
|
||||
export function mapParticipantsNamesToDatabase(
|
||||
seeding: Seeding,
|
||||
database: Participant[],
|
||||
positions?: number[],
|
||||
): ParticipantSlot[] {
|
||||
return mapParticipantsToDatabase("name", seeding, database, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns participant slots mapped to the instances stored in the database thanks to their id.
|
||||
*
|
||||
* @param seeding The seeding.
|
||||
* @param database The participants stored in the database.
|
||||
* @param positions An optional list of positions (seeds) for a manual ordering.
|
||||
*/
|
||||
export function mapParticipantsIdsToDatabase(
|
||||
seeding: Seeding,
|
||||
database: Participant[],
|
||||
positions?: number[],
|
||||
): ParticipantSlot[] {
|
||||
return mapParticipantsToDatabase("id", seeding, database, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns participant slots mapped to the instances stored in the database thanks to a property of theirs.
|
||||
*
|
||||
* @param prop The property to search participants with.
|
||||
* @param seeding The seeding.
|
||||
* @param database The participants stored in the database.
|
||||
* @param positions An optional list of positions (seeds) for a manual ordering.
|
||||
*/
|
||||
export function mapParticipantsToDatabase(
|
||||
prop: "id" | "name",
|
||||
seeding: Seeding,
|
||||
database: Participant[],
|
||||
positions?: number[],
|
||||
): ParticipantSlot[] {
|
||||
const slots = seeding.map((slot, i) => {
|
||||
if (slot === null) return null; // BYE.
|
||||
|
||||
const found = database.find((participant) =>
|
||||
typeof slot === "object"
|
||||
? participant[prop] === slot[prop]
|
||||
: participant[prop] === slot,
|
||||
);
|
||||
|
||||
if (!found) throw Error(`Participant ${prop} not found in database.`);
|
||||
|
||||
return { id: found.id, position: i + 1 };
|
||||
});
|
||||
|
||||
if (!positions) return slots;
|
||||
|
||||
if (positions.length !== slots.length)
|
||||
throw Error(
|
||||
"Not enough seeds in at least one group of the manual ordering.",
|
||||
);
|
||||
|
||||
return positions.map((position) => slots[position - 1]); // Because `position` is `i + 1`.
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of matches to a seeding.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -78,11 +78,6 @@ export class BracketsManager {
|
|||
public import(data: Database, normalizeIds = false): void {
|
||||
if (normalizeIds) data = helpers.normalizeIds(data);
|
||||
|
||||
if (!this.storage.delete("participant"))
|
||||
throw Error("Could not empty the participant table.");
|
||||
if (!this.storage.insert("participant", data.participant))
|
||||
throw Error("Could not import participants.");
|
||||
|
||||
if (!this.storage.delete("stage"))
|
||||
throw Error("Could not empty the stage table.");
|
||||
if (!this.storage.insert("stage", data.stage))
|
||||
|
|
@ -108,9 +103,6 @@ export class BracketsManager {
|
|||
* Exports data from the database.
|
||||
*/
|
||||
public export(): Database {
|
||||
const participants = this.storage.select("participant");
|
||||
if (!participants) throw Error("Error getting participants.");
|
||||
|
||||
const stages = this.storage.select("stage");
|
||||
if (!stages) throw Error("Error getting stages.");
|
||||
|
||||
|
|
@ -124,7 +116,6 @@ export class BracketsManager {
|
|||
if (!matches) throw Error("Error getting matches.");
|
||||
|
||||
return {
|
||||
participant: participants,
|
||||
stage: stages,
|
||||
group: groups,
|
||||
round: rounds,
|
||||
|
|
|
|||
|
|
@ -51,13 +51,4 @@ export class Reset extends BaseUpdater {
|
|||
if (!helpers.isRoundRobin(stage) && !helpers.isSwiss(stage))
|
||||
this.updateRelatedMatches(stored, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the seeding of a stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
public seeding(stageId: number): void {
|
||||
this.updateSeeding(stageId, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,68 +2,10 @@ import * as assert from "uvu/assert";
|
|||
import { BracketsManager } from "../manager";
|
||||
import { InMemoryDatabase } from "~/modules/brackets-memory-db";
|
||||
import { suite } from "uvu";
|
||||
import type { InputStage } from "~/modules/brackets-model";
|
||||
|
||||
const storage = new InMemoryDatabase();
|
||||
const manager = new BracketsManager(storage);
|
||||
|
||||
const createTournament = (tournamentType: any): InputStage => ({
|
||||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: tournamentType,
|
||||
seeding: [
|
||||
{ name: "Team 1", nationality: "US", tournament_id: 0, id: 0 },
|
||||
{ name: "Team 2", nationality: "US", tournament_id: 0, id: 1 },
|
||||
{ name: "Team 3", nationality: "US", tournament_id: 0, id: 2 },
|
||||
{ name: "Team 4", nationality: "US", tournament_id: 0, id: 3 },
|
||||
{ name: "Team 5", nationality: "US", tournament_id: 0, id: 4 },
|
||||
{ name: "Team 6", nationality: "US", tournament_id: 0, id: 5 },
|
||||
{ name: "Team 7", nationality: "US", tournament_id: 0, id: 6 },
|
||||
{ name: "Team 8", nationality: "US", tournament_id: 0, id: 7 },
|
||||
{ name: "Team 9", nationality: "US", tournament_id: 0, id: 8 },
|
||||
{ name: "Team 10", nationality: "US", tournament_id: 0, id: 9 },
|
||||
{ name: "Team 11", nationality: "US", tournament_id: 0, id: 10 },
|
||||
{ name: "Team 12", nationality: "US", tournament_id: 0, id: 11 },
|
||||
{ name: "Team 13", nationality: "US", tournament_id: 0, id: 12 },
|
||||
{ name: "Team 14", nationality: "US", tournament_id: 0, id: 13 },
|
||||
{ name: "Team 15", nationality: "US", tournament_id: 0, id: 14 },
|
||||
{ name: "Team 16", nationality: "US", tournament_id: 0, id: 15 },
|
||||
],
|
||||
settings:
|
||||
tournamentType === "round_robin"
|
||||
? { groupCount: 2 }
|
||||
: { seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
const CustomSeeding = suite("Create tournaments with custom seeding");
|
||||
|
||||
CustomSeeding.before.each(() => {
|
||||
storage.reset();
|
||||
});
|
||||
|
||||
CustomSeeding("should create single elimination with custom seeding", () => {
|
||||
manager.create(createTournament("single_elimination"));
|
||||
const stageData = manager.get.stageData(0);
|
||||
assert.equal((stageData.participant[0] as any).nationality, "US");
|
||||
assert.equal(stageData.participant.length, 16);
|
||||
});
|
||||
|
||||
CustomSeeding("should create double elimination with custom seeding", () => {
|
||||
manager.create(createTournament("double_elimination"));
|
||||
const stageData = manager.get.stageData(0);
|
||||
|
||||
assert.equal((stageData.participant[0] as any).nationality, "US");
|
||||
assert.equal(stageData.participant.length, 16);
|
||||
});
|
||||
|
||||
CustomSeeding("should create round robin with custom seeding", () => {
|
||||
manager.create(createTournament("round_robin"));
|
||||
const stageData = manager.get.stageData(0);
|
||||
|
||||
assert.equal((stageData.participant[0] as any).nationality, "US");
|
||||
assert.equal(stageData.participant.length, 16);
|
||||
});
|
||||
|
||||
const ExtraFields = suite("Update results with extra fields");
|
||||
|
||||
ExtraFields.before.each(() => {
|
||||
|
|
@ -75,7 +17,7 @@ ExtraFields("Extra fields when updating a match", () => {
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.update.match({
|
||||
|
|
@ -127,5 +69,4 @@ ExtraFields("Extra fields when updating a match", () => {
|
|||
});
|
||||
});
|
||||
|
||||
CustomSeeding.run();
|
||||
ExtraFields.run();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ DeleteStage("should delete a stage and all its linked data", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.delete.stage(0);
|
||||
|
|
@ -38,14 +38,14 @@ DeleteStage("should delete one stage and only its linked data", () => {
|
|||
name: "Example 1",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.create({
|
||||
name: "Example 2",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.delete.stage(0);
|
||||
|
|
@ -72,14 +72,14 @@ DeleteStage("should delete all stages of the tournament", () => {
|
|||
name: "Example 1",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.create({
|
||||
name: "Example 2",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
manager.delete.tournament(0);
|
||||
|
|
|
|||
|
|
@ -18,24 +18,7 @@ CreateDoubleElimination("should create a double elimination stage", () => {
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: { seedOrdering: ["natural"], grandFinal: "simple" },
|
||||
});
|
||||
|
||||
|
|
@ -48,30 +31,6 @@ CreateDoubleElimination("should create a double elimination stage", () => {
|
|||
assert.equal(storage.select<any>("match")!.length, 30);
|
||||
});
|
||||
|
||||
CreateDoubleElimination(
|
||||
"should create a double elimination stage with only two participants",
|
||||
() => {
|
||||
// This is an edge case. No lower bracket nor grand final will be created.
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
settings: { size: 2 },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("group")!.length, 1);
|
||||
assert.equal(storage.select<any>("round")!.length, 1);
|
||||
assert.equal(storage.select<any>("match")!.length, 1);
|
||||
|
||||
// Ensure update works.
|
||||
manager.update.seeding(0, ["Team 1", "Team 2"]);
|
||||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: { result: "win" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
CreateDoubleElimination(
|
||||
"should create a tournament with 256+ tournaments",
|
||||
() => {
|
||||
|
|
@ -91,16 +50,7 @@ CreateDoubleElimination(
|
|||
name: "Example with double grand final",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: { grandFinal: "double", seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
|
|
@ -125,24 +75,7 @@ MatchUpdateDoubleElimination(
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: { seedOrdering: ["natural"], grandFinal: "simple" },
|
||||
});
|
||||
|
||||
|
|
@ -196,7 +129,7 @@ MatchUpdateDoubleElimination(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", null],
|
||||
seeding: [1, 2, 3, null],
|
||||
settings: { grandFinal: "simple" },
|
||||
});
|
||||
|
||||
|
|
@ -234,7 +167,7 @@ MatchUpdateDoubleElimination("should determine matches in grand final", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { grandFinal: "double" },
|
||||
});
|
||||
|
||||
|
|
@ -306,7 +239,7 @@ MatchUpdateDoubleElimination(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { grandFinal: "double" },
|
||||
});
|
||||
|
||||
|
|
@ -338,24 +271,7 @@ MatchUpdateDoubleElimination(
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: {
|
||||
seedOrdering: ["natural", "reverse", "reverse"],
|
||||
grandFinal: "simple",
|
||||
|
|
@ -397,16 +313,7 @@ MatchUpdateDoubleElimination(
|
|||
name: "Example with inner_outer loser ordering",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
seedOrdering: ["inner_outer", "inner_outer"],
|
||||
},
|
||||
|
|
@ -423,7 +330,7 @@ MatchUpdateDoubleElimination(
|
|||
opponent1: { result: "win" }, // Loser id: 7.
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 7);
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 8);
|
||||
|
||||
// Match of position 2.
|
||||
manager.update.match({
|
||||
|
|
@ -431,7 +338,7 @@ MatchUpdateDoubleElimination(
|
|||
opponent1: { result: "win" }, // Loser id: 4.
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 8).opponent1.id, 4);
|
||||
assert.equal(storage.select<any>("match", 8).opponent1.id, 5);
|
||||
|
||||
// Match of position 3.
|
||||
manager.update.match({
|
||||
|
|
@ -439,7 +346,7 @@ MatchUpdateDoubleElimination(
|
|||
opponent1: { result: "win" }, // Loser id: 6.
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 8).opponent2.id, 6);
|
||||
assert.equal(storage.select<any>("match", 8).opponent2.id, 7);
|
||||
|
||||
// Match of position 4.
|
||||
manager.update.match({
|
||||
|
|
@ -447,7 +354,7 @@ MatchUpdateDoubleElimination(
|
|||
opponent1: { result: "win" }, // Loser id: 5.
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 7).opponent2.id, 5);
|
||||
assert.equal(storage.select<any>("match", 7).opponent2.id, 6);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -460,24 +367,7 @@ SkipFirstRoundDoubleElimination.before.each(() => {
|
|||
name: "Example with double grand final",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
skipFirstRound: true,
|
||||
|
|
@ -498,8 +388,8 @@ SkipFirstRoundDoubleElimination(
|
|||
|
||||
assert.equal(storage.select<any>("round", 0).number, 1); // Even though the "real" first round is skipped, the stored first round's number should be 1.
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0); // First match of WB.
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 1); // First match of LB.
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 1); // First match of WB.
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 2); // First match of LB.
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -507,31 +397,31 @@ SkipFirstRoundDoubleElimination(
|
|||
"should choose the correct previous and next matches",
|
||||
() => {
|
||||
manager.update.match({ id: 0, opponent1: { result: "win" } });
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 1); // First match of LB Round 1 (must stay).
|
||||
assert.equal(storage.select<any>("match", 12).opponent1.id, 2); // First match of LB Round 2 (must be updated).
|
||||
assert.equal(storage.select<any>("match", 7).opponent1.id, 2); // First match of LB Round 1 (must stay).
|
||||
assert.equal(storage.select<any>("match", 12).opponent1.id, 3); // First match of LB Round 2 (must be updated).
|
||||
|
||||
manager.update.match({ id: 1, opponent1: { result: "win" } });
|
||||
assert.equal(storage.select<any>("match", 7).opponent2.id, 3); // First match of LB Round 1 (must stay).
|
||||
assert.equal(storage.select<any>("match", 11).opponent1.id, 6); // Second match of LB Round 2 (must be updated).
|
||||
assert.equal(storage.select<any>("match", 7).opponent2.id, 4); // First match of LB Round 1 (must stay).
|
||||
assert.equal(storage.select<any>("match", 11).opponent1.id, 7); // Second match of LB Round 2 (must be updated).
|
||||
|
||||
manager.update.match({ id: 4, opponent1: { result: "win" } }); // First match of WB Round 2.
|
||||
assert.equal(storage.select<any>("match", 18).opponent1.id, 4); // First match of LB Round 4.
|
||||
assert.equal(storage.select<any>("match", 18).opponent1.id, 5); // First match of LB Round 4.
|
||||
|
||||
manager.update.match({ id: 7, opponent1: { result: "win" } }); // First match of LB Round 1.
|
||||
assert.equal(storage.select<any>("match", 11).opponent2.id, 1); // First match of LB Round 2.
|
||||
assert.equal(storage.select<any>("match", 11).opponent2.id, 2); // First match of LB Round 2.
|
||||
|
||||
for (let i = 2; i < 21; i++)
|
||||
manager.update.match({ id: i, opponent1: { result: "win" } });
|
||||
|
||||
assert.equal(storage.select<any>("match", 15).opponent1.id, 6); // First match of LB Round 3.
|
||||
assert.equal(storage.select<any>("match", 15).opponent1.id, 7); // First match of LB Round 3.
|
||||
|
||||
assert.equal(storage.select<any>("match", 21).opponent1.id, 0); // GF Round 1.
|
||||
assert.equal(storage.select<any>("match", 21).opponent2.id, 8); // GF Round 1.
|
||||
assert.equal(storage.select<any>("match", 21).opponent1.id, 1); // GF Round 1.
|
||||
assert.equal(storage.select<any>("match", 21).opponent2.id, 9); // GF Round 1.
|
||||
|
||||
manager.update.match({ id: 21, opponent2: { result: "win" } });
|
||||
|
||||
assert.equal(storage.select<any>("match", 21).opponent1.id, 0); // GF Round 2.
|
||||
assert.equal(storage.select<any>("match", 22).opponent2.id, 8); // GF Round 2.
|
||||
assert.equal(storage.select<any>("match", 21).opponent1.id, 1); // GF Round 2.
|
||||
assert.equal(storage.select<any>("match", 22).opponent2.id, 9); // GF Round 2.
|
||||
|
||||
manager.update.match({ id: 22, opponent2: { result: "win" } });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,16 +19,7 @@ FindSingleElimination("should find previous matches", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
});
|
||||
|
||||
const beforeFirst = manager.find.previousMatches(0);
|
||||
|
|
@ -55,16 +46,7 @@ FindSingleElimination("should find next matches", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
});
|
||||
|
||||
const afterFirst = manager.find.nextMatches(0);
|
||||
|
|
@ -86,33 +68,24 @@ FindSingleElimination(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
});
|
||||
|
||||
manager.update.match({ id: 0, opponent1: { result: "loss" } });
|
||||
const afterFirstEliminated = manager.find.nextMatches(0, 0);
|
||||
const afterFirstEliminated = manager.find.nextMatches(0, 1);
|
||||
assert.equal(afterFirstEliminated.length, 0);
|
||||
const afterFirstContinued = manager.find.nextMatches(0, 1);
|
||||
const afterFirstContinued = manager.find.nextMatches(0, 2);
|
||||
assert.equal(afterFirstContinued.length, 1);
|
||||
|
||||
manager.update.match({ id: 1, opponent1: { result: "win" } });
|
||||
const beforeSemi1Up = manager.find.previousMatches(4, 1);
|
||||
const beforeSemi1Up = manager.find.previousMatches(4, 2);
|
||||
assert.equal(beforeSemi1Up.length, 1);
|
||||
assert.equal(beforeSemi1Up[0].id, 0);
|
||||
|
||||
const beforeSemi1Down = manager.find.previousMatches(4, 2);
|
||||
const beforeSemi1Down = manager.find.previousMatches(4, 3);
|
||||
assert.equal(beforeSemi1Down.length, 1);
|
||||
assert.equal(beforeSemi1Down[0].id, 1);
|
||||
},
|
||||
|
|
@ -131,16 +104,7 @@ FindDoubleElimination("should find previous matches", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
});
|
||||
|
||||
const beforeFirstWB = manager.find.previousMatches(0);
|
||||
|
|
@ -187,16 +151,7 @@ FindDoubleElimination("should find next matches", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
});
|
||||
|
||||
const afterFirstWB = manager.find.nextMatches(0);
|
||||
|
|
@ -236,33 +191,33 @@ FindDoubleElimination(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
});
|
||||
|
||||
manager.update.match({ id: 0, opponent1: { result: "loss" } });
|
||||
const afterFirstEliminated = manager.find.nextMatches(0, 0);
|
||||
const afterFirstEliminated = manager.find.nextMatches(0, 1);
|
||||
assert.equal(afterFirstEliminated.length, 1);
|
||||
assert.equal(afterFirstEliminated[0].id, 3);
|
||||
const afterFirstContinued = manager.find.nextMatches(0, 1);
|
||||
const afterFirstContinued = manager.find.nextMatches(0, 2);
|
||||
assert.equal(afterFirstContinued.length, 1);
|
||||
assert.equal(afterFirstContinued[0].id, 2);
|
||||
|
||||
manager.update.match({ id: 1, opponent1: { result: "win" } });
|
||||
const beforeSemi1Up = manager.find.previousMatches(2, 1);
|
||||
const beforeSemi1Up = manager.find.previousMatches(2, 2);
|
||||
assert.equal(beforeSemi1Up.length, 1);
|
||||
assert.equal(beforeSemi1Up[0].id, 0);
|
||||
|
||||
const beforeSemi1Down = manager.find.previousMatches(2, 2);
|
||||
const beforeSemi1Down = manager.find.previousMatches(2, 3);
|
||||
assert.equal(beforeSemi1Down.length, 1);
|
||||
assert.equal(beforeSemi1Down[0].id, 1);
|
||||
|
||||
manager.update.match({ id: 3, opponent1: { result: "loss" } });
|
||||
const afterLowerBracketEliminated = manager.find.nextMatches(3, 0);
|
||||
const afterLowerBracketEliminated = manager.find.nextMatches(3, 1);
|
||||
assert.equal(afterLowerBracketEliminated.length, 0);
|
||||
const afterLowerBracketContinued = manager.find.nextMatches(3, 3);
|
||||
const afterLowerBracketContinued = manager.find.nextMatches(3, 4);
|
||||
assert.equal(afterLowerBracketContinued.length, 1);
|
||||
assert.equal(afterLowerBracketContinued[0].id, 4);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ BYEHandling("should propagate BYEs through the brackets", () => {
|
|||
name: "Example with BYEs",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", null, null, null],
|
||||
seeding: [1, null, null, null],
|
||||
settings: { seedOrdering: ["natural"], grandFinal: "simple" },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 2).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 2).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 2).opponent2, null);
|
||||
|
||||
assert.equal(storage.select<any>("match", 3).opponent1, null);
|
||||
|
|
@ -30,7 +30,7 @@ BYEHandling("should propagate BYEs through the brackets", () => {
|
|||
assert.equal(storage.select<any>("match", 4).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 4).opponent2, null);
|
||||
|
||||
assert.equal(storage.select<any>("match", 5).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 5).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 5).opponent2, null);
|
||||
});
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ BYEHandling("should handle incomplete seeding during creation", () => {
|
|||
name: "Example with BYEs",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2"],
|
||||
seeding: [1, 2],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
balanceByes: false, // Default value.
|
||||
|
|
@ -47,8 +47,8 @@ BYEHandling("should handle incomplete seeding during creation", () => {
|
|||
},
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2.id, 1);
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2.id, 2);
|
||||
|
||||
assert.equal(storage.select<any>("match", 1).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 1).opponent2, null);
|
||||
|
|
@ -59,7 +59,7 @@ BYEHandling("should balance BYEs in the seeding", () => {
|
|||
name: "Example with BYEs",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2"],
|
||||
seeding: [1, 2],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
balanceByes: true,
|
||||
|
|
@ -67,10 +67,10 @@ BYEHandling("should balance BYEs in the seeding", () => {
|
|||
},
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2, null);
|
||||
|
||||
assert.equal(storage.select<any>("match", 1).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 1).opponent1.id, 2);
|
||||
assert.equal(storage.select<any>("match", 1).opponent2, null);
|
||||
});
|
||||
|
||||
|
|
@ -131,42 +131,6 @@ SpecialCases.before.each(() => {
|
|||
storage.reset();
|
||||
});
|
||||
|
||||
SpecialCases(
|
||||
"should create a stage and add participants IDs in seeding",
|
||||
() => {
|
||||
const teams = [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
];
|
||||
|
||||
const participants = teams.map((name) => ({
|
||||
tournament_id: 0,
|
||||
name,
|
||||
}));
|
||||
|
||||
// Simulation of external database filling for participants.
|
||||
storage.insert("participant", participants);
|
||||
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
settings: { size: 8 },
|
||||
});
|
||||
|
||||
// Update seeding with already existing IDs.
|
||||
manager.update.seeding(0, [0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
},
|
||||
);
|
||||
|
||||
SpecialCases("should throw if the name of the stage is not provided", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
|
|
@ -203,15 +167,7 @@ SpecialCases(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7],
|
||||
}),
|
||||
"The library only supports a participant count which is a power of two.",
|
||||
);
|
||||
|
|
@ -267,24 +223,7 @@ SeedingAndOrderingInElimination.before.each(() => {
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: {
|
||||
seedOrdering: [
|
||||
"inner_outer",
|
||||
|
|
@ -429,7 +368,7 @@ ResetMatchAndMatchGames("should reset results of a match", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2"],
|
||||
seeding: [1, 2],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
size: 8,
|
||||
|
|
@ -478,7 +417,7 @@ ResetMatchAndMatchGames(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
|
|
@ -517,7 +456,7 @@ ImportExport("should import data in the storage", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
|
|
@ -552,90 +491,12 @@ ImportExport("should import data in the storage", () => {
|
|||
assert.equal(storage.select<any>("match", 1).opponent1.result, undefined);
|
||||
});
|
||||
|
||||
ImportExport("should import data in the storage with normalized IDs", () => {
|
||||
storage.insert("participant", { name: "Unused team" });
|
||||
|
||||
manager.create({
|
||||
name: "Example 1",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
settings: {
|
||||
groupCount: 1,
|
||||
},
|
||||
});
|
||||
|
||||
manager.create({
|
||||
name: "Example 2",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 5", "Team 6", "Team 7", "Team 8"],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
});
|
||||
|
||||
const initialData = manager.get.stageData(1);
|
||||
|
||||
assert.equal(initialData.stage[0].id, 1);
|
||||
assert.equal(initialData.participant[0], {
|
||||
id: 1,
|
||||
tournament_id: 0,
|
||||
name: "Team 1",
|
||||
});
|
||||
assert.equal(initialData.group[0], { id: 1, stage_id: 1, number: 1 });
|
||||
assert.equal(initialData.round[0], {
|
||||
id: 3,
|
||||
stage_id: 1,
|
||||
group_id: 1,
|
||||
number: 1,
|
||||
});
|
||||
assert.equal(initialData.match[0], {
|
||||
id: 6,
|
||||
stage_id: 1,
|
||||
group_id: 1,
|
||||
round_id: 3,
|
||||
opponent1: { id: 5, position: 1 },
|
||||
opponent2: { id: 6, position: 2 },
|
||||
number: 1,
|
||||
status: 2,
|
||||
});
|
||||
|
||||
manager.import(initialData, true);
|
||||
|
||||
const data = manager.get.stageData(0);
|
||||
|
||||
assert.equal(data.stage[0].id, 0);
|
||||
assert.equal(data.participant[0], {
|
||||
id: 0,
|
||||
tournament_id: 0,
|
||||
name: "Team 1",
|
||||
});
|
||||
assert.equal(data.group[0], { id: 0, stage_id: 0, number: 1 });
|
||||
assert.equal(data.round[0], {
|
||||
id: 0,
|
||||
stage_id: 0,
|
||||
group_id: 0,
|
||||
number: 1,
|
||||
});
|
||||
assert.equal(data.match[0], {
|
||||
id: 0,
|
||||
stage_id: 0,
|
||||
group_id: 0,
|
||||
round_id: 0,
|
||||
opponent1: { id: 4, position: 1 },
|
||||
opponent2: { id: 5, position: 2 },
|
||||
number: 1,
|
||||
status: 2,
|
||||
});
|
||||
});
|
||||
|
||||
ImportExport("should export data from the storage", () => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
|
|
@ -643,11 +504,10 @@ ImportExport("should export data from the storage", () => {
|
|||
|
||||
const data = manager.export();
|
||||
|
||||
for (const key of ["participant", "stage", "group", "round", "match"]) {
|
||||
for (const key of ["stage", "group", "round", "match"]) {
|
||||
assert.ok(Object.keys(data).includes(key));
|
||||
}
|
||||
|
||||
assert.equal(storage.select<any>("participant"), data.participant);
|
||||
assert.equal(storage.select<any>("stage"), data.stage);
|
||||
assert.equal(storage.select<any>("group"), data.group);
|
||||
assert.equal(storage.select<any>("round"), data.round);
|
||||
|
|
|
|||
|
|
@ -6,195 +6,6 @@ import * as assert from "uvu/assert";
|
|||
const storage = new InMemoryDatabase();
|
||||
const manager = new BracketsManager(storage);
|
||||
|
||||
const GetFinalStandings = suite("Get final standings");
|
||||
|
||||
GetFinalStandings.before.each(() => {
|
||||
storage.reset();
|
||||
});
|
||||
|
||||
GetFinalStandings(
|
||||
"should get the final standings for a single elimination stage with consolation final",
|
||||
() => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
settings: { consolationFinal: true },
|
||||
});
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
manager.update.match({
|
||||
id: i,
|
||||
...(i % 2 === 0
|
||||
? { opponent1: { result: "win" } }
|
||||
: { opponent2: { result: "win" } }),
|
||||
});
|
||||
}
|
||||
|
||||
const finalStandings = manager.get.finalStandings(0);
|
||||
|
||||
assert.equal(finalStandings, [
|
||||
{ id: 0, name: "Team 1", rank: 1 },
|
||||
{ id: 5, name: "Team 6", rank: 2 },
|
||||
|
||||
// The consolation final has inverted those ones (rank 3).
|
||||
{ id: 1, name: "Team 2", rank: 3 },
|
||||
{ id: 4, name: "Team 5", rank: 4 },
|
||||
|
||||
{ id: 7, name: "Team 8", rank: 5 },
|
||||
{ id: 3, name: "Team 4", rank: 5 },
|
||||
{ id: 6, name: "Team 7", rank: 5 },
|
||||
{ id: 2, name: "Team 3", rank: 5 },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
GetFinalStandings(
|
||||
"should get the final standings for a single elimination stage without consolation final",
|
||||
() => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
settings: { consolationFinal: false },
|
||||
});
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
manager.update.match({
|
||||
id: i,
|
||||
...(i % 2 === 0
|
||||
? { opponent1: { result: "win" } }
|
||||
: { opponent2: { result: "win" } }),
|
||||
});
|
||||
}
|
||||
|
||||
const finalStandings = manager.get.finalStandings(0);
|
||||
|
||||
assert.equal(finalStandings, [
|
||||
{ id: 0, name: "Team 1", rank: 1 },
|
||||
{ id: 5, name: "Team 6", rank: 2 },
|
||||
|
||||
// Here, they are not inverted (rank 3).
|
||||
{ id: 4, name: "Team 5", rank: 3 },
|
||||
{ id: 1, name: "Team 2", rank: 3 },
|
||||
|
||||
{ id: 7, name: "Team 8", rank: 4 },
|
||||
{ id: 3, name: "Team 4", rank: 4 },
|
||||
{ id: 6, name: "Team 7", rank: 4 },
|
||||
{ id: 2, name: "Team 3", rank: 4 },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
GetFinalStandings(
|
||||
"should get the final standings for a double elimination stage with a grand final",
|
||||
() => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
settings: { grandFinal: "double" },
|
||||
});
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
manager.update.match({
|
||||
id: i,
|
||||
...(i % 2 === 0
|
||||
? { opponent1: { result: "win" } }
|
||||
: { opponent2: { result: "win" } }),
|
||||
});
|
||||
}
|
||||
|
||||
const finalStandings = manager.get.finalStandings(0);
|
||||
|
||||
assert.equal(finalStandings, [
|
||||
{ id: 0, name: "Team 1", rank: 1 },
|
||||
{ id: 5, name: "Team 6", rank: 2 },
|
||||
{ id: 4, name: "Team 5", rank: 3 },
|
||||
{ id: 3, name: "Team 4", rank: 4 },
|
||||
{ id: 1, name: "Team 2", rank: 5 },
|
||||
{ id: 6, name: "Team 7", rank: 5 },
|
||||
{ id: 7, name: "Team 8", rank: 6 },
|
||||
{ id: 2, name: "Team 3", rank: 6 },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
GetFinalStandings(
|
||||
"should get the final standings for a double elimination stage without a grand final",
|
||||
() => {
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
settings: { grandFinal: "none" },
|
||||
});
|
||||
|
||||
for (let i = 0; i < 13; i++) {
|
||||
manager.update.match({
|
||||
id: i,
|
||||
// The parity is reversed here, just to have different results.
|
||||
...(i % 2 === 1
|
||||
? { opponent1: { result: "win" } }
|
||||
: { opponent2: { result: "win" } }),
|
||||
});
|
||||
}
|
||||
|
||||
const finalStandings = manager.get.finalStandings(0);
|
||||
|
||||
assert.equal(finalStandings, [
|
||||
{ id: 6, name: "Team 7", rank: 1 },
|
||||
{ id: 2, name: "Team 3", rank: 2 },
|
||||
{ id: 3, name: "Team 4", rank: 3 },
|
||||
{ id: 5, name: "Team 6", rank: 4 },
|
||||
{ id: 0, name: "Team 1", rank: 5 },
|
||||
{ id: 7, name: "Team 8", rank: 5 },
|
||||
{ id: 4, name: "Team 5", rank: 6 },
|
||||
{ id: 1, name: "Team 2", rank: 6 },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
const GetSeeding = suite("Get seeding");
|
||||
|
||||
GetSeeding("should get the seeding of a round-robin stage", () => {
|
||||
|
|
@ -228,44 +39,13 @@ GetSeeding("should get the seeding of a round-robin stage with BYEs", () => {
|
|||
groupCount: 2,
|
||||
size: 8,
|
||||
},
|
||||
seeding: ["Team 1", null, null, null, null, null, null, null],
|
||||
seeding: [1, null, null, null, null, null, null, null],
|
||||
});
|
||||
|
||||
const seeding = manager.get.seeding(0);
|
||||
assert.equal(seeding.length, 8);
|
||||
});
|
||||
|
||||
GetSeeding(
|
||||
"should get the seeding of a round-robin stage with BYEs after update",
|
||||
() => {
|
||||
storage.reset();
|
||||
|
||||
manager.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
settings: {
|
||||
groupCount: 2,
|
||||
size: 8,
|
||||
},
|
||||
});
|
||||
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
|
||||
const seeding = manager.get.seeding(0);
|
||||
assert.equal(seeding.length, 8);
|
||||
},
|
||||
);
|
||||
|
||||
GetSeeding("should get the seeding of a single elimination stage", () => {
|
||||
storage.reset();
|
||||
|
||||
|
|
@ -289,16 +69,7 @@ GetSeeding("should get the seeding with BYEs", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
null,
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
null,
|
||||
null,
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, null, 2, 3, 4, null, null, 5],
|
||||
settings: {
|
||||
seedOrdering: ["inner_outer"],
|
||||
},
|
||||
|
|
@ -307,16 +78,15 @@ GetSeeding("should get the seeding with BYEs", () => {
|
|||
const seeding = manager.get.seeding(0);
|
||||
assert.equal(seeding.length, 8);
|
||||
assert.equal(seeding, [
|
||||
{ id: 0, position: 1 },
|
||||
{ id: 1, position: 1 },
|
||||
null,
|
||||
{ id: 1, position: 3 },
|
||||
{ id: 2, position: 4 },
|
||||
{ id: 3, position: 5 },
|
||||
{ id: 2, position: 3 },
|
||||
{ id: 3, position: 4 },
|
||||
{ id: 4, position: 5 },
|
||||
null,
|
||||
null,
|
||||
{ id: 4, position: 8 },
|
||||
{ id: 5, position: 8 },
|
||||
]);
|
||||
});
|
||||
|
||||
GetFinalStandings.run();
|
||||
GetSeeding.run();
|
||||
|
|
|
|||
|
|
@ -17,16 +17,7 @@ CreateRoundRobinStage("should create a round-robin stage", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: { groupCount: 2 },
|
||||
} as any;
|
||||
|
||||
|
|
@ -48,16 +39,7 @@ CreateRoundRobinStage(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
groupCount: 2,
|
||||
manualOrdering: [
|
||||
|
|
@ -92,16 +74,7 @@ CreateRoundRobinStage(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
groupCount: 2,
|
||||
manualOrdering: [[1, 4, 6, 7]],
|
||||
|
|
@ -116,16 +89,7 @@ CreateRoundRobinStage(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
groupCount: 2,
|
||||
manualOrdering: [
|
||||
|
|
@ -146,16 +110,7 @@ CreateRoundRobinStage(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, null, null, null],
|
||||
settings: { groupCount: 2 },
|
||||
} as any;
|
||||
|
||||
|
|
@ -192,24 +147,15 @@ CreateRoundRobinStage(
|
|||
name: "Example with effort balanced",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {
|
||||
groupCount: 2,
|
||||
seedOrdering: ["groups.seed_optimized"],
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2.id, 7);
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 1);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2.id, 8);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -253,7 +199,7 @@ UpdateRoundRobinScores.before.each(() => {
|
|||
name: "Example scores",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { groupCount: 1 },
|
||||
});
|
||||
});
|
||||
|
|
@ -269,7 +215,7 @@ ExampleUseCase.before.each(() => {
|
|||
name: "Example scores",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: ["POCEBLO", "twitch.tv/mrs_fly", "Ballec Squad", "AQUELLEHEURE?!"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { groupCount: 1 },
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,24 +18,7 @@ CreateSingleEliminationStage("should create a single elimination stage", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: { seedOrdering: ["natural"] },
|
||||
} as any;
|
||||
|
||||
|
|
@ -57,20 +40,11 @@ CreateSingleEliminationStage(
|
|||
name: "Example with BYEs",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
null,
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
null,
|
||||
null,
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, null, 3, 4, null, null, 7, 8],
|
||||
settings: { seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 4).opponent1.id, 0); // Determined because of opponent's BYE.
|
||||
assert.equal(storage.select<any>("match", 4).opponent1.id, 1); // Determined because of opponent's BYE.
|
||||
assert.equal(storage.select<any>("match", 4).opponent2.id, null); // To be determined.
|
||||
assert.equal(storage.select<any>("match", 5).opponent1, null); // BYE propagated.
|
||||
assert.equal(storage.select<any>("match", 5).opponent2.id, null); // To be determined.
|
||||
|
|
@ -84,16 +58,7 @@ CreateSingleEliminationStage(
|
|||
name: "Example with consolation final",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: { consolationFinal: true, seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
|
|
@ -110,21 +75,12 @@ CreateSingleEliminationStage(
|
|||
name: "Example with consolation final and BYEs",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [null, null, null, 4, 5, 6, 7, 8],
|
||||
settings: { consolationFinal: true, seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 4).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 4).opponent2.id, 0);
|
||||
assert.equal(storage.select<any>("match", 4).opponent2.id, 4);
|
||||
|
||||
// Consolation final
|
||||
assert.equal(storage.select<any>("match", 7).opponent1, null);
|
||||
|
|
@ -139,16 +95,7 @@ CreateSingleEliminationStage(
|
|||
name: "Example with Bo3 matches",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: { seedOrdering: ["natural"] },
|
||||
});
|
||||
|
||||
|
|
@ -258,10 +205,10 @@ CreateSingleEliminationStage(
|
|||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 1", // Duplicate
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
1,
|
||||
1, // Duplicate
|
||||
3,
|
||||
4,
|
||||
],
|
||||
}),
|
||||
"The seeding has a duplicate participant.",
|
||||
|
|
@ -276,7 +223,7 @@ CreateSingleEliminationStage(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
|
|
@ -303,7 +250,7 @@ PreviousAndNextMatchUpdate(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { consolationFinal: true },
|
||||
});
|
||||
|
||||
|
|
@ -341,7 +288,7 @@ PreviousAndNextMatchUpdate(
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { consolationFinal: true },
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,24 +11,7 @@ const example = {
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
"Team 9",
|
||||
"Team 10",
|
||||
"Team 11",
|
||||
"Team 12",
|
||||
"Team 13",
|
||||
"Team 14",
|
||||
"Team 15",
|
||||
"Team 16",
|
||||
],
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
settings: { seedOrdering: ["natural"] },
|
||||
} as any;
|
||||
|
||||
|
|
@ -67,7 +50,7 @@ UpdateMatches(
|
|||
assert.equal(after.opponent1.score, 2);
|
||||
|
||||
// Name should stay. It shouldn't be overwritten.
|
||||
assert.equal(after.opponent1.id, 0);
|
||||
assert.equal(after.opponent1.id, 1);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -94,7 +77,7 @@ UpdateMatches(
|
|||
opponent1: { result: "win" },
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 8).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 8).opponent1.id, 1);
|
||||
|
||||
manager.update.match({
|
||||
id: 0,
|
||||
|
|
@ -108,7 +91,7 @@ UpdateMatches(
|
|||
|
||||
const nextMatch = storage.select<any>("match", 8);
|
||||
assert.equal(nextMatch.status, Status.Waiting);
|
||||
assert.equal(nextMatch.opponent1.id, 1);
|
||||
assert.equal(nextMatch.opponent1.id, 2);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -241,7 +224,7 @@ GiveOpponentIds.before.each(() => {
|
|||
name: "Amateur",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: ["Team 1", "Team 2", "Team 3", "Team 4"],
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: { seedOrdering: ["natural"] },
|
||||
});
|
||||
});
|
||||
|
|
@ -250,11 +233,11 @@ GiveOpponentIds("should update the right opponents based on their IDs", () => {
|
|||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: {
|
||||
id: 1,
|
||||
id: 2,
|
||||
score: 10,
|
||||
},
|
||||
opponent2: {
|
||||
id: 0,
|
||||
id: 1,
|
||||
score: 5,
|
||||
},
|
||||
});
|
||||
|
|
@ -271,7 +254,7 @@ GiveOpponentIds(
|
|||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: {
|
||||
id: 1,
|
||||
id: 2,
|
||||
score: 10,
|
||||
},
|
||||
});
|
||||
|
|
@ -291,7 +274,7 @@ GiveOpponentIds(
|
|||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: {
|
||||
id: 2, // Belongs to match id 1.
|
||||
id: 3, // Belongs to match id 1.
|
||||
score: 10,
|
||||
},
|
||||
}),
|
||||
|
|
@ -330,387 +313,6 @@ LockedMatches(
|
|||
},
|
||||
);
|
||||
|
||||
const Seeding = suite("Seeding");
|
||||
|
||||
Seeding.before.each(() => {
|
||||
storage.reset();
|
||||
|
||||
manager.create({
|
||||
name: "Without participants",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
settings: {
|
||||
size: 8,
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Seeding("should update the seeding in a stage without any participant", () => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("participant")!.length, 8);
|
||||
});
|
||||
|
||||
Seeding("should update the seeding to remove participants", () => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
null,
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
null,
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
|
||||
// In this context, a `null` value is not a BYE, but a TDB (to be determined)
|
||||
// because we consider the tournament might have been started.
|
||||
// If it's not and you prefer BYEs, just recreate the tournament.
|
||||
assert.equal(storage.select<any>("match", 1).opponent1.id, null);
|
||||
assert.equal(storage.select<any>("match", 3).opponent2.id, null);
|
||||
});
|
||||
|
||||
Seeding("should handle incomplete seeding during seeding update", () => {
|
||||
manager.update.seeding(0, ["Team 1", "Team 2"]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 0).opponent2.id, 1);
|
||||
|
||||
// Same here, see comments above.
|
||||
assert.equal(storage.select<any>("match", 1).opponent1.id, null);
|
||||
assert.equal(storage.select<any>("match", 1).opponent2.id, null);
|
||||
});
|
||||
|
||||
Seeding("should update BYE to TBD during seeding update", () => {
|
||||
storage.reset();
|
||||
|
||||
manager.create({
|
||||
name: "With participants and BYEs",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [
|
||||
null,
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
],
|
||||
settings: {
|
||||
seedOrdering: ["natural"],
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1, null);
|
||||
|
||||
manager.update.seeding(0, [
|
||||
null,
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
// To stay consistent with the fact that `update.seeding()` uses TBD and not BYE,
|
||||
// the BYE should be updated to TDB here.
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, null);
|
||||
});
|
||||
|
||||
Seeding("should reset the seeding of a stage", () => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.reset.seeding(0);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, null);
|
||||
assert.equal(storage.select<any>("participant")!.length, 8); // Participants aren't removed.
|
||||
});
|
||||
|
||||
Seeding(
|
||||
"should update the seeding in a stage with participants already",
|
||||
() => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.update.seeding(0, [
|
||||
"Team A",
|
||||
"Team B",
|
||||
"Team C",
|
||||
"Team D",
|
||||
"Team E",
|
||||
"Team F",
|
||||
"Team G",
|
||||
"Team H",
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 8);
|
||||
assert.equal(storage.select<any>("participant")!.length, 16);
|
||||
},
|
||||
);
|
||||
|
||||
Seeding(
|
||||
"should update the seeding in a stage by registering only one missing participant",
|
||||
() => {
|
||||
manager.update.seeding(0, [
|
||||
"Team A",
|
||||
"Team B",
|
||||
"Team C",
|
||||
"Team D",
|
||||
"Team E",
|
||||
"Team F",
|
||||
"Team G",
|
||||
"Team H",
|
||||
]);
|
||||
|
||||
manager.update.seeding(0, [
|
||||
"Team A",
|
||||
"Team B", // Match 0.
|
||||
"Team C",
|
||||
"Team D", // Match 1.
|
||||
"Team E",
|
||||
"Team F", // Match 2.
|
||||
"Team G",
|
||||
"Team Z", // Match 3.
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 0);
|
||||
assert.equal(storage.select<any>("match", 3).opponent2.id, 8);
|
||||
assert.equal(storage.select<any>("participant")!.length, 9);
|
||||
},
|
||||
);
|
||||
|
||||
Seeding("should update the seeding in a stage on non-locked matches", () => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.update.match({
|
||||
id: 2, // Any match id.
|
||||
opponent1: { score: 1 },
|
||||
opponent2: { score: 0 },
|
||||
});
|
||||
|
||||
manager.update.seeding(0, [
|
||||
"Team A",
|
||||
"Team B", // Match 0.
|
||||
"Team C",
|
||||
"Team D", // Match 1.
|
||||
"Team 5",
|
||||
"Team 6", // Match 2. NO CHANGE.
|
||||
"Team G",
|
||||
"Team H", // Match 3.
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 0).opponent1.id, 8); // New id.
|
||||
assert.equal(storage.select<any>("match", 2).opponent1.id, 4); // Still old id.
|
||||
assert.equal(storage.select<any>("participant")!.length, 8 + 6);
|
||||
});
|
||||
|
||||
Seeding(
|
||||
"should update the seeding and keep completed matches completed",
|
||||
() => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: { score: 1, result: "win" },
|
||||
opponent2: { score: 0 },
|
||||
});
|
||||
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2", // Keep this pair.
|
||||
"Team 4",
|
||||
"Team 3",
|
||||
"Team 6",
|
||||
"Team 5",
|
||||
"Team 8",
|
||||
"Team 7",
|
||||
]);
|
||||
|
||||
const match = storage.select<any>("match", 0);
|
||||
assert.equal(match.opponent1.result, "win");
|
||||
assert.equal(match.status, Status.Completed);
|
||||
},
|
||||
);
|
||||
|
||||
Seeding(
|
||||
"should throw if a match is completed and would have to be changed",
|
||||
() => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.update.match({
|
||||
id: 0,
|
||||
opponent1: { score: 1, result: "win" },
|
||||
opponent2: { score: 0 },
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
manager.update.seeding(0, [
|
||||
"Team 2",
|
||||
"Team 1", // Change this pair.
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]),
|
||||
"A match is locked.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Seeding(
|
||||
"should throw if a match is locked and would have to be changed",
|
||||
() => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]);
|
||||
|
||||
manager.update.match({
|
||||
id: 2, // Any match id.
|
||||
opponent1: { score: 1 },
|
||||
opponent2: { score: 0 },
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
manager.update.seeding(0, [
|
||||
"Team A",
|
||||
"Team B", // Match 0.
|
||||
"Team C",
|
||||
"Team D", // Match 1.
|
||||
"WILL",
|
||||
"THROW", // Match 2.
|
||||
"Team G",
|
||||
"Team H", // Match 3.
|
||||
]),
|
||||
"A match is locked.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Seeding("should throw if the seeding has duplicate participants", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 1", // Duplicate
|
||||
"Team 3",
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
"Team 6",
|
||||
"Team 7",
|
||||
"Team 8",
|
||||
]),
|
||||
"The seeding has a duplicate participant.",
|
||||
);
|
||||
});
|
||||
|
||||
Seeding("should confirm the current seeding", () => {
|
||||
manager.update.seeding(0, [
|
||||
"Team 1",
|
||||
"Team 2",
|
||||
null,
|
||||
"Team 4",
|
||||
"Team 5",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
|
||||
assert.equal(storage.select<any>("match", 1).opponent1.id, null); // First, is a TBD.
|
||||
assert.equal(storage.select<any>("match", 2).opponent2.id, null);
|
||||
assert.equal(storage.select<any>("match", 3).opponent1.id, null);
|
||||
assert.equal(storage.select<any>("match", 3).opponent2.id, null);
|
||||
|
||||
manager.update.confirmSeeding(0);
|
||||
|
||||
assert.equal(storage.select<any>("participant")!.length, 4);
|
||||
|
||||
assert.equal(storage.select<any>("match", 1).opponent1, null); // Should become a BYE.
|
||||
assert.equal(storage.select<any>("match", 2).opponent2, null);
|
||||
assert.equal(storage.select<any>("match", 3).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 3).opponent2, null);
|
||||
|
||||
assert.equal(storage.select<any>("match", 5).opponent2, null); // A BYE should be propagated here.
|
||||
|
||||
assert.equal(storage.select<any>("match", 7).opponent2, null); // All of these too (in loser bracket).
|
||||
assert.equal(storage.select<any>("match", 8).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 8).opponent2, null);
|
||||
assert.equal(storage.select<any>("match", 9).opponent1, null);
|
||||
assert.equal(storage.select<any>("match", 10).opponent2, null);
|
||||
});
|
||||
|
||||
UpdateMatches.run();
|
||||
GiveOpponentIds.run();
|
||||
LockedMatches.run();
|
||||
Seeding.run();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type {
|
||||
Group,
|
||||
Match,
|
||||
Participant,
|
||||
Round,
|
||||
SeedOrdering,
|
||||
Stage,
|
||||
|
|
@ -90,7 +89,6 @@ export interface DataTypes {
|
|||
group: Group;
|
||||
round: Round;
|
||||
match: Match;
|
||||
participant: Participant;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import type {
|
||||
Match,
|
||||
Round,
|
||||
Seeding,
|
||||
SeedOrdering,
|
||||
} from "~/modules/brackets-model";
|
||||
import type { Match, Round, SeedOrdering } from "~/modules/brackets-model";
|
||||
import { Status } from "~/modules/brackets-model";
|
||||
import { ordering } from "./ordering";
|
||||
import { BaseUpdater } from "./base/updater";
|
||||
|
|
@ -65,27 +60,6 @@ export class Update extends BaseUpdater {
|
|||
this.updateRoundOrdering(round, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the seeding of a stage.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
* @param seeding The new seeding.
|
||||
*/
|
||||
public seeding(stageId: number, seeding: Seeding): void {
|
||||
this.updateSeeding(stageId, seeding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the seeding of a stage.
|
||||
*
|
||||
* This will convert TBDs to BYEs and propagate them.
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
public confirmSeeding(stageId: number): void {
|
||||
this.confirmCurrentSeeding(stageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the seed ordering of a round.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type {
|
|||
|
||||
export class InMemoryDatabase implements CrudInterface {
|
||||
protected data: Database = {
|
||||
participant: [],
|
||||
stage: [],
|
||||
group: [],
|
||||
round: [],
|
||||
|
|
@ -40,7 +39,6 @@ export class InMemoryDatabase implements CrudInterface {
|
|||
*/
|
||||
reset(): void {
|
||||
this.data = {
|
||||
participant: [],
|
||||
stage: [],
|
||||
group: [],
|
||||
round: [],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
* Contains everything which is given by the user as input.
|
||||
*-----------------------------------------------------------*/
|
||||
|
||||
import type { Participant } from "./storage";
|
||||
import type {
|
||||
GrandFinalType,
|
||||
RoundRobinMode,
|
||||
|
|
@ -10,12 +9,6 @@ import type {
|
|||
StageType,
|
||||
} from "./unions";
|
||||
|
||||
/**
|
||||
* A participant as it would be persisted in the storage, but with extra fields.
|
||||
*/
|
||||
export type CustomParticipant<ExtraFields = Record<string, unknown>> =
|
||||
Participant & ExtraFields;
|
||||
|
||||
/**
|
||||
* The seeding for a stage.
|
||||
*
|
||||
|
|
@ -25,7 +18,7 @@ export type CustomParticipant<ExtraFields = Record<string, unknown>> =
|
|||
* - Its ID.
|
||||
* - Or a BYE: `null`.
|
||||
*/
|
||||
export type Seeding = (CustomParticipant | string | number | null)[];
|
||||
export type Seeding = (number | null)[];
|
||||
|
||||
/**
|
||||
* Used to create a stage.
|
||||
|
|
|
|||
|
|
@ -10,16 +10,6 @@ import type { StageType } from "./unions";
|
|||
/**
|
||||
* A participant of a stage (team or individual).
|
||||
*/
|
||||
export interface Participant {
|
||||
/** ID of the participant. */
|
||||
id: number;
|
||||
|
||||
/** ID of the tournament this participant belongs to. */
|
||||
tournament_id: number;
|
||||
|
||||
/** Name of the participant. */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A stage, which can be a round-robin stage or a single/double elimination stage.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user