From dea2760d9fa95653d1cbd08973a3533f2e8fa415 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 23 Feb 2025 11:09:11 +0200 Subject: [PATCH] Fix wonky group letters after Z (many teams) --- .../components/Bracket/RoundRobin.tsx | 4 ++-- .../components/Bracket/Swiss.tsx | 4 ++-- .../components/StartedMatch.tsx | 6 +++--- .../tournament-bracket/core/Tournament.ts | 12 ++++++------ .../routes/to.$id.matches.$mid.tsx | 6 +++--- .../tournament-bracket-utils.test.ts | 19 +++++++++++++++++++ .../tournament-bracket-utils.ts | 19 +++++++++++++++++-- 7 files changed, 52 insertions(+), 18 deletions(-) diff --git a/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx b/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx index eacfa7e52..1b5ea7c0b 100644 --- a/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx +++ b/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx @@ -1,6 +1,6 @@ import type { Match as MatchType } from "~/modules/brackets-model"; import type { Bracket as BracketType } from "../../core/Bracket"; -import { groupNumberToLetter } from "../../tournament-bracket-utils"; +import { groupNumberToLetters } from "../../tournament-bracket-utils"; import { Match } from "./Match"; import { PlacementsTable } from "./PlacementsTable"; import { RoundHeader } from "./RoundHeader"; @@ -107,7 +107,7 @@ function getGroups(bracket: BracketType) { ); result.push({ - groupName: `Group ${groupNumberToLetter(group.number)}`, + groupName: `Group ${groupNumberToLetters(group.number)}`, matches, groupId: group.id, }); diff --git a/app/features/tournament-bracket/components/Bracket/Swiss.tsx b/app/features/tournament-bracket/components/Bracket/Swiss.tsx index c49aab983..5d4625ed3 100644 --- a/app/features/tournament-bracket/components/Bracket/Swiss.tsx +++ b/app/features/tournament-bracket/components/Bracket/Swiss.tsx @@ -11,7 +11,7 @@ import { import { useSearchParamState } from "~/hooks/useSearchParamState"; import type { Match as MatchType } from "~/modules/brackets-model"; import type { Bracket as BracketType } from "../../core/Bracket"; -import { groupNumberToLetter } from "../../tournament-bracket-utils"; +import { groupNumberToLetters } from "../../tournament-bracket-utils"; import { Match } from "./Match"; import { PlacementsTable } from "./PlacementsTable"; import { RoundHeader } from "./RoundHeader"; @@ -259,7 +259,7 @@ function getGroups(bracket: BracketType) { ); result.push({ - groupName: `Group ${groupNumberToLetter(group.number)}`, + groupName: `Group ${groupNumberToLetters(group.number)}`, matches, groupId: group.id, }); diff --git a/app/features/tournament-bracket/components/StartedMatch.tsx b/app/features/tournament-bracket/components/StartedMatch.tsx index 1ffd40d11..743ae0da7 100644 --- a/app/features/tournament-bracket/components/StartedMatch.tsx +++ b/app/features/tournament-bracket/components/StartedMatch.tsx @@ -34,7 +34,7 @@ import * as PickBan from "../core/PickBan"; import type { TournamentDataTeam } from "../core/Tournament.server"; import type { TournamentMatchLoaderData } from "../routes/to.$id.matches.$mid"; import { - groupNumberToLetter, + groupNumberToLetters, mapCountPlayedInSetWithCertainty, matchIsLocked, pickInfoText, @@ -102,9 +102,9 @@ export function StartedMatch({ .find((group) => group.id === match?.group_id); return tournament.resolvePoolCode({ hostingTeamId, - groupLetter: + groupLetters: group && bracket?.type === "round_robin" - ? groupNumberToLetter(group.number) + ? groupNumberToLetters(group.number) : undefined, bracketNumber: hasRoundRobin && bracket?.type !== "round_robin" diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index 73afcc682..278bf628b 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -25,7 +25,7 @@ import { assertUnreachable } from "~/utils/types"; import { userSubmittedImage } from "~/utils/urls"; import { fillWithNullTillPowerOfTwo, - groupNumberToLetter, + groupNumberToLetters, } from "../tournament-bracket-utils"; import { Bracket } from "./Bracket"; import * as Swiss from "./Swiss"; @@ -684,11 +684,11 @@ export class Tournament { resolvePoolCode({ hostingTeamId, - groupLetter, + groupLetters, bracketNumber, }: { hostingTeamId: number; - groupLetter?: string; + groupLetters?: string; bracketNumber?: number; }) { const tournamentNameWithoutOnlyLetters = this.ctx.name.replace( @@ -716,7 +716,7 @@ export class Tournament { return { prefix, suffix: - globalSuffix ?? groupLetter ?? bracketNumber ?? hostingTeamId % 10, + globalSuffix ?? groupLetters ?? bracketNumber ?? hostingTeamId % 10, }; } @@ -956,7 +956,7 @@ export class Tournament { (round) => round.id === match.round_id, ); - roundName = `Groups ${group?.number ? groupNumberToLetter(group.number) : ""}${round?.number ?? ""}.${match.number}`; + roundName = `Groups ${group?.number ? groupNumberToLetters(group.number) : ""}${round?.number ?? ""}.${match.number}`; } else if (bracket.type === "swiss") { const group = bracket.data.group.find( (group) => group.id === match.group_id, @@ -967,7 +967,7 @@ export class Tournament { const oneGroupOnly = bracket.data.group.length === 1; - roundName = `Swiss${oneGroupOnly ? "" : " Group"} ${group?.number && !oneGroupOnly ? groupNumberToLetter(group.number) : ""} ${round?.number ?? ""}.${match.number}`; + roundName = `Swiss${oneGroupOnly ? "" : " Group"} ${group?.number && !oneGroupOnly ? groupNumberToLetters(group.number) : ""} ${round?.number ?? ""}.${match.number}`; } else if ( bracket.type === "single_elimination" || bracket.type === "double_elimination" diff --git a/app/features/tournament-bracket/routes/to.$id.matches.$mid.tsx b/app/features/tournament-bracket/routes/to.$id.matches.$mid.tsx index b82b60469..6589726e7 100644 --- a/app/features/tournament-bracket/routes/to.$id.matches.$mid.tsx +++ b/app/features/tournament-bracket/routes/to.$id.matches.$mid.tsx @@ -59,7 +59,7 @@ import { } from "../tournament-bracket-schemas.server"; import { bracketSubscriptionKey, - groupNumberToLetter, + groupNumberToLetters, isSetOverByScore, matchIsLocked, matchSubscriptionKey, @@ -745,7 +745,7 @@ function MatchHeader() { (round) => round.id === match.round_id, ); - roundName = `Groups ${group?.number ? groupNumberToLetter(group.number) : ""}${round?.number ?? ""}.${match.number}`; + roundName = `Groups ${group?.number ? groupNumberToLetters(group.number) : ""}${round?.number ?? ""}.${match.number}`; } else if (bracket.type === "swiss") { const group = bracket.data.group.find( (group) => group.id === match.group_id, @@ -756,7 +756,7 @@ function MatchHeader() { const oneGroupOnly = bracket.data.group.length === 1; - roundName = `Swiss${oneGroupOnly ? "" : " Group"} ${group?.number && !oneGroupOnly ? groupNumberToLetter(group.number) : ""} ${round?.number ?? ""}.${match.number}`; + roundName = `Swiss${oneGroupOnly ? "" : " Group"} ${group?.number && !oneGroupOnly ? groupNumberToLetters(group.number) : ""} ${round?.number ?? ""}.${match.number}`; } else if ( bracket.type === "single_elimination" || bracket.type === "double_elimination" diff --git a/app/features/tournament-bracket/tournament-bracket-utils.test.ts b/app/features/tournament-bracket/tournament-bracket-utils.test.ts index c645307d0..e20f2f487 100644 --- a/app/features/tournament-bracket/tournament-bracket-utils.test.ts +++ b/app/features/tournament-bracket/tournament-bracket-utils.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "vitest"; import { fillWithNullTillPowerOfTwo, + groupNumberToLetters, mapCountPlayedInSetWithCertainty, } from "./tournament-bracket-utils"; @@ -53,3 +54,21 @@ describe("fillWithNullTillPowerOfTwo()", () => { }); } }); + +const groupNumberToLettersParamsToResult = [ + { groupNumber: 1, expected: "A" }, + { groupNumber: 26, expected: "Z" }, + { groupNumber: 27, expected: "AA" }, + { groupNumber: 52, expected: "AZ" }, + { groupNumber: 53, expected: "BA" }, + { groupNumber: 702, expected: "ZZ" }, + { groupNumber: 703, expected: "AAA" }, +]; + +describe("groupNumberToLetters()", () => { + for (const { groupNumber, expected } of groupNumberToLettersParamsToResult) { + test(`groupNumber=${groupNumber} -> ${expected}`, () => { + expect(groupNumberToLetters(groupNumber)).toBe(expected); + }); + } +}); diff --git a/app/features/tournament-bracket/tournament-bracket-utils.ts b/app/features/tournament-bracket/tournament-bracket-utils.ts index 1c9e884d4..a4868532c 100644 --- a/app/features/tournament-bracket/tournament-bracket-utils.ts +++ b/app/features/tournament-bracket/tournament-bracket-utils.ts @@ -206,8 +206,23 @@ export function pickInfoText({ return ""; } -export function groupNumberToLetter(groupNumber: number) { - return String.fromCharCode(65 + groupNumber - 1).toUpperCase(); +/** + * Converts a group number to its corresponding letter representation. + * + * The function takes a one-based group number and converts it to a string + * of uppercase letters, similar to how Excel columns are labeled (e.g., 1 -> 'A', 26 -> 'Z', 27 -> 'AA'). + * + * @param groupNumber - The one-based group number to convert. + * @returns The letter representation of the group number. + */ +export function groupNumberToLetters(groupNumber: number) { + let letters = ""; + let num = groupNumber - 1; // Adjust for one-based input + while (num >= 0) { + letters = String.fromCharCode((num % 26) + 65) + letters; + num = Math.floor(num / 26) - 1; + } + return letters; } export function isSetOverByResults({