mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-27 13:58:59 -05:00
109 lines
3.1 KiB
TypeScript
109 lines
3.1 KiB
TypeScript
import type { TournamentBadgeReceivers } from "~/features/tournament-bracket/tournament-bracket-schemas.server";
|
|
import type { TournamentLoaderData } from "../tournament/loaders/to.$id.server";
|
|
import type { Standing } from "./core/Bracket";
|
|
|
|
export const tournamentWebsocketRoom = (tournamentId: number) =>
|
|
`tournament__${tournamentId}`;
|
|
|
|
/**
|
|
* 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 tournamentTeamToActiveRosterUserIds(
|
|
team: TournamentLoaderData["tournament"]["ctx"]["teams"][number],
|
|
teamMinMemberCount: number,
|
|
) {
|
|
if (
|
|
team.activeRosterUserIds &&
|
|
team.activeRosterUserIds.length === teamMinMemberCount
|
|
) {
|
|
return team.activeRosterUserIds;
|
|
}
|
|
|
|
// they don't need to select active roster as they have no subs
|
|
if (team.members.length === teamMinMemberCount) {
|
|
return team.members.map((member) => member.userId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// deal with user getting added to multiple teams by the TO
|
|
export function ensureOneStandingPerUser(standings: Standing[]) {
|
|
const userIds = new Set<number>();
|
|
|
|
return standings.map((standing) => {
|
|
return {
|
|
...standing,
|
|
team: {
|
|
...standing.team,
|
|
members: standing.team.members.filter((member) => {
|
|
if (userIds.has(member.userId)) return false;
|
|
userIds.add(member.userId);
|
|
return true;
|
|
}),
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validates the assignment of badges to receivers in a tournament finalization context.
|
|
*
|
|
* Checks the following conditions:
|
|
* - Each badge receiver references a valid badge from the provided list.
|
|
* - Every badge has at least one assigned receiver (both team and at least one user).
|
|
* - No duplicate tournament team IDs exist among the badge receivers.
|
|
*
|
|
* Returns `null` if all validations pass.
|
|
*/
|
|
export function validateBadgeReceivers({
|
|
badgeReceivers,
|
|
badges,
|
|
}: {
|
|
badgeReceivers: TournamentBadgeReceivers;
|
|
badges: ReadonlyArray<{ id: number }>;
|
|
}) {
|
|
if (
|
|
badgeReceivers.some(
|
|
(receiver) => !badges.some((badge) => badge.id === receiver.badgeId),
|
|
)
|
|
) {
|
|
return "BADGE_NOT_FOUND";
|
|
}
|
|
|
|
for (const badge of badges) {
|
|
const owner = badgeReceivers.find(
|
|
(receiver) => receiver.badgeId === badge.id,
|
|
);
|
|
if (!owner || owner.userIds.length === 0) {
|
|
return "BADGE_NOT_ASSIGNED";
|
|
}
|
|
}
|
|
|
|
const tournamentTeamIds = badgeReceivers.map(
|
|
(receiver) => receiver.tournamentTeamId,
|
|
);
|
|
const uniqueTournamentTeamIds = new Set(tournamentTeamIds);
|
|
if (tournamentTeamIds.length !== uniqueTournamentTeamIds.size) {
|
|
return "DUPLICATE_TOURNAMENT_TEAM_ID";
|
|
}
|
|
|
|
return null;
|
|
}
|