From dcf738f6d3f8e55ef01b02cffece880c45ff06e5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:53:31 +0300 Subject: [PATCH] Remove unused participant code + slim down tournament response --- .../tournament-bracket/core/Bracket.ts | 64 +- app/features/tournament-bracket/core/Swiss.ts | 27 +- .../core/Tournament.test.ts | 14 +- .../tournament-bracket/core/Tournament.ts | 58 +- .../core/brackets-manager/crud-db.server.ts | 30 +- .../tournament-bracket/core/tests/mocks.ts | 607 ------------------ .../core/tests/round-robin.test.ts | 289 --------- .../core/tests/test-utils.ts | 12 +- .../routes/to.$id.brackets.tsx | 10 +- app/modules/brackets-manager/base/updater.ts | 102 +-- app/modules/brackets-manager/create.ts | 83 +-- app/modules/brackets-manager/get.ts | 180 +----- app/modules/brackets-manager/helpers.ts | 177 +---- app/modules/brackets-manager/manager.ts | 9 - app/modules/brackets-manager/reset.ts | 9 - .../brackets-manager/test/custom.test.ts | 61 +- .../brackets-manager/test/delete.test.ts | 10 +- .../test/double-elimination.test.ts | 162 +---- .../brackets-manager/test/find.test.ts | 77 +-- .../brackets-manager/test/general.test.ts | 172 +---- app/modules/brackets-manager/test/get.test.ts | 244 +------ .../brackets-manager/test/round-robin.test.ts | 74 +-- .../test/single-elimination.test.ts | 81 +-- .../brackets-manager/test/update.test.ts | 416 +----------- app/modules/brackets-manager/types.ts | 2 - app/modules/brackets-manager/update.ts | 28 +- app/modules/brackets-memory-db/index.ts | 2 - app/modules/brackets-model/input.ts | 9 +- app/modules/brackets-model/storage.ts | 10 - 29 files changed, 225 insertions(+), 2794 deletions(-) delete mode 100644 app/features/tournament-bracket/core/tests/round-robin.test.ts diff --git a/app/features/tournament-bracket/core/Bracket.ts b/app/features/tournament-bracket/core/Bracket.ts index 2b85c5b2b..c4d778e25 100644 --- a/app/features/tournament-bracket/core/Bracket.ts +++ b/app/features/tournament-bracket/core/Bracket.ts @@ -14,7 +14,7 @@ import type { BracketMapCounts } from "./toMapList"; interface CreateBracketArgs { id: number; preview: boolean; - data: ValueToArray; + data: Omit, "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) => { + const resolveLosersGroupId = ( + data: Omit, "participant">, + ) => { const minGroupId = Math.min(...data.round.map((round) => round.group_id)); return minGroupId + 1; }; const placementsToRoundsIds = ( - data: ValueToArray, + data: Omit, "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), }; } diff --git a/app/features/tournament-bracket/core/Swiss.ts b/app/features/tournament-bracket/core/Swiss.ts index e665ec3c2..b26d9749e 100644 --- a/app/features/tournament-bracket/core/Swiss.ts +++ b/app/features/tournament-bracket/core/Swiss.ts @@ -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 { - seeding: readonly SwissSeeding[]; -} - -export function create(args: CreateArgs): ValueToArray { +export function create( + args: Omit, +): ValueToArray { const swissSettings = args.settings?.swiss; const groupCount = swissSettings?.groupCount ?? 1; @@ -29,11 +25,6 @@ export function create(args: CreateArgs): ValueToArray { 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; diff --git a/app/features/tournament-bracket/core/Tournament.test.ts b/app/features/tournament-bracket/core/Tournament.test.ts index 320b8e234..e18a093fe 100644 --- a/app/features/tournament-bracket/core/Tournament.test.ts +++ b/app/features/tournament-bracket/core/Tournament.test.ts @@ -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(); diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index 0e6cce2e7..e8c23234a 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -86,7 +86,7 @@ export class Tournament { return a.createdAt - b.createdAt; } - private initBrackets(data: ValueToArray) { + private initBrackets(data: Omit, "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, ) { - 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, ); - 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; diff --git a/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts b/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts index e09de7a80..4dec32d0f 100644 --- a/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts +++ b/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts @@ -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, ); diff --git a/app/features/tournament-bracket/core/tests/mocks.ts b/app/features/tournament-bracket/core/tests/mocks.ts index fd73ecd4e..10bb4487c 100644 --- a/app/features/tournament-bracket/core/tests/mocks.ts +++ b/app/features/tournament-bracket/core/tests/mocks.ts @@ -142,28 +142,6 @@ export const FOUR_TEAMS_RR = (): ValueToArray => ({ }, }, ], - 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 => ({ @@ -383,33 +361,6 @@ export const FIVE_TEAMS_RR = (): ValueToArray => ({ }, }, ], - 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 => ({ @@ -576,38 +527,6 @@ export const SIX_TEAMS_TWO_GROUPS_RR = (): ValueToArray => ({ }, }, ], - 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, diff --git a/app/features/tournament-bracket/core/tests/round-robin.test.ts b/app/features/tournament-bracket/core/tests/round-robin.test.ts deleted file mode 100644 index aea1267ad..000000000 --- a/app/features/tournament-bracket/core/tests/round-robin.test.ts +++ /dev/null @@ -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 = { - 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(); diff --git a/app/features/tournament-bracket/core/tests/test-utils.ts b/app/features/tournament-bracket/core/tests/test-utils.ts index c800c4572..42080b7dd 100644 --- a/app/features/tournament-bracket/core/tests/test-utils.ts +++ b/app/features/tournament-bracket/core/tests/test-utils.ts @@ -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, partialCtx?: Partial, ) => { + 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, diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.tsx b/app/features/tournament-bracket/routes/to.$id.brackets.tsx index 71bc3c5f9..c0675ba82 100644 --- a/app/features/tournament-bracket/routes/to.$id.brackets.tsx +++ b/app/features/tournament-bracket/routes/to.$id.brackets.tsx @@ -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 (
@@ -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 ? ( @@ -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 (
✅ Your team is checked in to the bracket (ask the TO for a check-out if diff --git a/app/modules/brackets-manager/base/updater.ts b/app/modules/brackets-manager/base/updater.ts index 24bb445a5..0f6b7ce43 100644 --- a/app/modules/brackets-manager/base/updater.ts +++ b/app/modules/brackets-manager/base/updater.ts @@ -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. * diff --git a/app/modules/brackets-manager/create.ts b/app/modules/brackets-manager/create.ts index cdfc0217e..fac49f8bb 100644 --- a/app/modules/brackets-manager/create.ts +++ b/app/modules/brackets-manager/create.ts @@ -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[]): 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. */ diff --git a/app/modules/brackets-manager/get.ts b/app/modules/brackets-manager/get.ts index af7fe9313..7c8a06d25 100644 --- a/app/modules/brackets-manager/get.ts +++ b/app/modules/brackets-manager/get.ts @@ -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; -}; diff --git a/app/modules/brackets-manager/helpers.ts b/app/modules/brackets-manager/helpers.ts index 30f4d20de..dfbc4a222 100644 --- a/app/modules/brackets-manager/helpers.ts +++ b/app/modules/brackets-manager/helpers.ts @@ -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[] { - const withoutByes = seeding.filter( - (name): name is /* number */ string | CustomParticipant => name !== null, - ); - - const participants = withoutByes.map>((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. * diff --git a/app/modules/brackets-manager/manager.ts b/app/modules/brackets-manager/manager.ts index f382ebaf8..c65b8e43b 100644 --- a/app/modules/brackets-manager/manager.ts +++ b/app/modules/brackets-manager/manager.ts @@ -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, diff --git a/app/modules/brackets-manager/reset.ts b/app/modules/brackets-manager/reset.ts index 372e14702..83ff510a7 100644 --- a/app/modules/brackets-manager/reset.ts +++ b/app/modules/brackets-manager/reset.ts @@ -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); - } } diff --git a/app/modules/brackets-manager/test/custom.test.ts b/app/modules/brackets-manager/test/custom.test.ts index e87010127..70cdf032a 100644 --- a/app/modules/brackets-manager/test/custom.test.ts +++ b/app/modules/brackets-manager/test/custom.test.ts @@ -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(); diff --git a/app/modules/brackets-manager/test/delete.test.ts b/app/modules/brackets-manager/test/delete.test.ts index 395a6c115..88271141a 100644 --- a/app/modules/brackets-manager/test/delete.test.ts +++ b/app/modules/brackets-manager/test/delete.test.ts @@ -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); diff --git a/app/modules/brackets-manager/test/double-elimination.test.ts b/app/modules/brackets-manager/test/double-elimination.test.ts index c8b6a405b..e9b20d9a5 100644 --- a/app/modules/brackets-manager/test/double-elimination.test.ts +++ b/app/modules/brackets-manager/test/double-elimination.test.ts @@ -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("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("group")!.length, 1); - assert.equal(storage.select("round")!.length, 1); - assert.equal(storage.select("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("match", 7).opponent1.id, 7); + assert.equal(storage.select("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("match", 8).opponent1.id, 4); + assert.equal(storage.select("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("match", 8).opponent2.id, 6); + assert.equal(storage.select("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("match", 7).opponent2.id, 5); + assert.equal(storage.select("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("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("match", 0).opponent1.id, 0); // First match of WB. - assert.equal(storage.select("match", 7).opponent1.id, 1); // First match of LB. + assert.equal(storage.select("match", 0).opponent1.id, 1); // First match of WB. + assert.equal(storage.select("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("match", 7).opponent1.id, 1); // First match of LB Round 1 (must stay). - assert.equal(storage.select("match", 12).opponent1.id, 2); // First match of LB Round 2 (must be updated). + assert.equal(storage.select("match", 7).opponent1.id, 2); // First match of LB Round 1 (must stay). + assert.equal(storage.select("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("match", 7).opponent2.id, 3); // First match of LB Round 1 (must stay). - assert.equal(storage.select("match", 11).opponent1.id, 6); // Second match of LB Round 2 (must be updated). + assert.equal(storage.select("match", 7).opponent2.id, 4); // First match of LB Round 1 (must stay). + assert.equal(storage.select("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("match", 18).opponent1.id, 4); // First match of LB Round 4. + assert.equal(storage.select("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("match", 11).opponent2.id, 1); // First match of LB Round 2. + assert.equal(storage.select("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("match", 15).opponent1.id, 6); // First match of LB Round 3. + assert.equal(storage.select("match", 15).opponent1.id, 7); // First match of LB Round 3. - assert.equal(storage.select("match", 21).opponent1.id, 0); // GF Round 1. - assert.equal(storage.select("match", 21).opponent2.id, 8); // GF Round 1. + assert.equal(storage.select("match", 21).opponent1.id, 1); // GF Round 1. + assert.equal(storage.select("match", 21).opponent2.id, 9); // GF Round 1. manager.update.match({ id: 21, opponent2: { result: "win" } }); - assert.equal(storage.select("match", 21).opponent1.id, 0); // GF Round 2. - assert.equal(storage.select("match", 22).opponent2.id, 8); // GF Round 2. + assert.equal(storage.select("match", 21).opponent1.id, 1); // GF Round 2. + assert.equal(storage.select("match", 22).opponent2.id, 9); // GF Round 2. manager.update.match({ id: 22, opponent2: { result: "win" } }); }, diff --git a/app/modules/brackets-manager/test/find.test.ts b/app/modules/brackets-manager/test/find.test.ts index 571dd8126..4b92592c9 100644 --- a/app/modules/brackets-manager/test/find.test.ts +++ b/app/modules/brackets-manager/test/find.test.ts @@ -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); diff --git a/app/modules/brackets-manager/test/general.test.ts b/app/modules/brackets-manager/test/general.test.ts index 0b7a77c0f..94fbeba96 100644 --- a/app/modules/brackets-manager/test/general.test.ts +++ b/app/modules/brackets-manager/test/general.test.ts @@ -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("match", 2).opponent1.id, 0); + assert.equal(storage.select("match", 2).opponent1.id, 1); assert.equal(storage.select("match", 2).opponent2, null); assert.equal(storage.select("match", 3).opponent1, null); @@ -30,7 +30,7 @@ BYEHandling("should propagate BYEs through the brackets", () => { assert.equal(storage.select("match", 4).opponent1, null); assert.equal(storage.select("match", 4).opponent2, null); - assert.equal(storage.select("match", 5).opponent1.id, 0); + assert.equal(storage.select("match", 5).opponent1.id, 1); assert.equal(storage.select("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("match", 0).opponent1.id, 0); - assert.equal(storage.select("match", 0).opponent2.id, 1); + assert.equal(storage.select("match", 0).opponent1.id, 1); + assert.equal(storage.select("match", 0).opponent2.id, 2); assert.equal(storage.select("match", 1).opponent1, null); assert.equal(storage.select("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("match", 0).opponent1.id, 0); + assert.equal(storage.select("match", 0).opponent1.id, 1); assert.equal(storage.select("match", 0).opponent2, null); - assert.equal(storage.select("match", 1).opponent1.id, 1); + assert.equal(storage.select("match", 1).opponent1.id, 2); assert.equal(storage.select("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("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("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("participant"), data.participant); assert.equal(storage.select("stage"), data.stage); assert.equal(storage.select("group"), data.group); assert.equal(storage.select("round"), data.round); diff --git a/app/modules/brackets-manager/test/get.test.ts b/app/modules/brackets-manager/test/get.test.ts index 2b2c35829..f926fda3d 100644 --- a/app/modules/brackets-manager/test/get.test.ts +++ b/app/modules/brackets-manager/test/get.test.ts @@ -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(); diff --git a/app/modules/brackets-manager/test/round-robin.test.ts b/app/modules/brackets-manager/test/round-robin.test.ts index fafec4455..a0dd529d7 100644 --- a/app/modules/brackets-manager/test/round-robin.test.ts +++ b/app/modules/brackets-manager/test/round-robin.test.ts @@ -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("match", 0).opponent1.id, 0); - assert.equal(storage.select("match", 0).opponent2.id, 7); + assert.equal(storage.select("match", 0).opponent1.id, 1); + assert.equal(storage.select("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 }, }); }); diff --git a/app/modules/brackets-manager/test/single-elimination.test.ts b/app/modules/brackets-manager/test/single-elimination.test.ts index 20c39ae08..492ff879d 100644 --- a/app/modules/brackets-manager/test/single-elimination.test.ts +++ b/app/modules/brackets-manager/test/single-elimination.test.ts @@ -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("match", 4).opponent1.id, 0); // Determined because of opponent's BYE. + assert.equal(storage.select("match", 4).opponent1.id, 1); // Determined because of opponent's BYE. assert.equal(storage.select("match", 4).opponent2.id, null); // To be determined. assert.equal(storage.select("match", 5).opponent1, null); // BYE propagated. assert.equal(storage.select("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("match", 4).opponent1, null); - assert.equal(storage.select("match", 4).opponent2.id, 0); + assert.equal(storage.select("match", 4).opponent2.id, 4); // Consolation final assert.equal(storage.select("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 }, }); diff --git a/app/modules/brackets-manager/test/update.test.ts b/app/modules/brackets-manager/test/update.test.ts index 20b04d3e0..810a49a42 100644 --- a/app/modules/brackets-manager/test/update.test.ts +++ b/app/modules/brackets-manager/test/update.test.ts @@ -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("match", 8).opponent1.id, 0); + assert.equal(storage.select("match", 8).opponent1.id, 1); manager.update.match({ id: 0, @@ -108,7 +91,7 @@ UpdateMatches( const nextMatch = storage.select("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("match", 0).opponent1.id, 0); - assert.equal(storage.select("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("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("match", 1).opponent1.id, null); - assert.equal(storage.select("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("match", 0).opponent1.id, 0); - assert.equal(storage.select("match", 0).opponent2.id, 1); - - // Same here, see comments above. - assert.equal(storage.select("match", 1).opponent1.id, null); - assert.equal(storage.select("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("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("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("match", 0).opponent1.id, null); - assert.equal(storage.select("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("match", 0).opponent1.id, 8); - assert.equal(storage.select("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("match", 0).opponent1.id, 0); - assert.equal(storage.select("match", 3).opponent2.id, 8); - assert.equal(storage.select("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("match", 0).opponent1.id, 8); // New id. - assert.equal(storage.select("match", 2).opponent1.id, 4); // Still old id. - assert.equal(storage.select("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("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("match", 1).opponent1.id, null); // First, is a TBD. - assert.equal(storage.select("match", 2).opponent2.id, null); - assert.equal(storage.select("match", 3).opponent1.id, null); - assert.equal(storage.select("match", 3).opponent2.id, null); - - manager.update.confirmSeeding(0); - - assert.equal(storage.select("participant")!.length, 4); - - assert.equal(storage.select("match", 1).opponent1, null); // Should become a BYE. - assert.equal(storage.select("match", 2).opponent2, null); - assert.equal(storage.select("match", 3).opponent1, null); - assert.equal(storage.select("match", 3).opponent2, null); - - assert.equal(storage.select("match", 5).opponent2, null); // A BYE should be propagated here. - - assert.equal(storage.select("match", 7).opponent2, null); // All of these too (in loser bracket). - assert.equal(storage.select("match", 8).opponent1, null); - assert.equal(storage.select("match", 8).opponent2, null); - assert.equal(storage.select("match", 9).opponent1, null); - assert.equal(storage.select("match", 10).opponent2, null); -}); - UpdateMatches.run(); GiveOpponentIds.run(); LockedMatches.run(); -Seeding.run(); diff --git a/app/modules/brackets-manager/types.ts b/app/modules/brackets-manager/types.ts index f0faf5742..d921a3689 100644 --- a/app/modules/brackets-manager/types.ts +++ b/app/modules/brackets-manager/types.ts @@ -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; } /** diff --git a/app/modules/brackets-manager/update.ts b/app/modules/brackets-manager/update.ts index 9e7a326b7..56d005a09 100644 --- a/app/modules/brackets-manager/update.ts +++ b/app/modules/brackets-manager/update.ts @@ -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. * diff --git a/app/modules/brackets-memory-db/index.ts b/app/modules/brackets-memory-db/index.ts index eac7e661e..940c606fc 100644 --- a/app/modules/brackets-memory-db/index.ts +++ b/app/modules/brackets-memory-db/index.ts @@ -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: [], diff --git a/app/modules/brackets-model/input.ts b/app/modules/brackets-model/input.ts index 58f3be97c..98a761221 100644 --- a/app/modules/brackets-model/input.ts +++ b/app/modules/brackets-model/input.ts @@ -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> = - Participant & ExtraFields; - /** * The seeding for a stage. * @@ -25,7 +18,7 @@ export type CustomParticipant> = * - Its ID. * - Or a BYE: `null`. */ -export type Seeding = (CustomParticipant | string | number | null)[]; +export type Seeding = (number | null)[]; /** * Used to create a stage. diff --git a/app/modules/brackets-model/storage.ts b/app/modules/brackets-model/storage.ts index e89495215..0bd7b9ab8 100644 --- a/app/modules/brackets-model/storage.ts +++ b/app/modules/brackets-model/storage.ts @@ -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.