diff --git a/app/components/tournament/InfoBanner.tsx b/app/components/tournament/InfoBanner.tsx index cb5a12991..3838cfcc1 100644 --- a/app/components/tournament/InfoBanner.tsx +++ b/app/components/tournament/InfoBanner.tsx @@ -1,7 +1,7 @@ import { Link, useLoaderData, useLocation } from "remix"; import { DiscordIcon } from "~/components/icons/Discord"; import { TwitterIcon } from "~/components/icons/Twitter"; -import { resolveTournamentFormatString } from "~/core/tournament/utils"; +import { resolveTournamentFormatString } from "~/core/tournament/bracket"; import { FindTournamentByNameForUrlI } from "~/services/tournament"; import { useUser } from "~/utils/hooks"; diff --git a/app/core/mapList.ts b/app/core/mapList.ts new file mode 100644 index 000000000..e6c78788d --- /dev/null +++ b/app/core/mapList.ts @@ -0,0 +1,31 @@ +import { Mode, Stage } from ".prisma/client"; +import { EliminationBracket } from "./tournament/bracket"; + +interface MapDesirability { + stageName: string; + value: number; + originalValue: number; + modes: Mode[]; + originalModes: Mode[]; +} + +export function generateMapListMapForRounds({ + mapPool, + rounds, +}: { + mapPool: Stage[]; + rounds: EliminationBracket; +}): EliminationBracket { + const modes = Array.from(new Set(mapPool.map((stage) => stage.mode))); + + return { + winners: rounds.winners.map((round) => roundsMapList(round)), + losers: rounds.losers.map((round) => roundsMapList(round)), + }; + + function roundsMapList(bestOf: number): Stage[] { + return new Array(bestOf) + .fill(null) + .map(() => ({ id: -1, name: "The Reef", mode: "SZ" })); + } +} diff --git a/app/core/tournament/algorithms.ts b/app/core/tournament/algorithms.ts index f994601f7..f57c02923 100644 --- a/app/core/tournament/algorithms.ts +++ b/app/core/tournament/algorithms.ts @@ -1,5 +1,4 @@ import invariant from "tiny-invariant"; -import type { Bracket, Match, TeamIdentifier } from "./bracket"; /** Singe/Double Elimination bracket algorithm that handles byes * @link https://stackoverflow.com/a/59615574 */ @@ -196,3 +195,23 @@ function createMatch(args: Omit): Match { ...args, }; } + +export type TeamIdentifier = number | "BYE"; + +export interface Match { + id: string; + upperTeam?: TeamIdentifier; + lowerTeam?: TeamIdentifier; + winner?: TeamIdentifier; + /** Match that leads to this match */ + match1?: Match; + /** Match that leads to this match */ + match2?: Match; +} + +export interface Bracket { + winners: Match[]; + losers: Match[]; + participantCount: number; + participantsWithByesCount: number; +} diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts index 83b7ed161..dab21779a 100644 --- a/app/core/tournament/bracket.ts +++ b/app/core/tournament/bracket.ts @@ -1,58 +1,47 @@ -import { BracketType, Stage } from ".prisma/client"; +import type { BracketType, Stage } from ".prisma/client"; import invariant from "tiny-invariant"; +import { generateMapListMapForRounds } from "../mapList"; +import type { Bracket } from "./algorithms"; -export function generateMaplistForTournament( - rounds: EliminationBracket< - { - bestOf: number; - }[] - > -): EliminationBracket< - { - mapList: Stage[][]; - }[] -> { - return { - winners: rounds.winners.map((round) => ({ mapList: [] })), - losers: rounds.losers.map((round) => ({ mapList: [] })), - }; -} - -export function roundNamesWithDefaultBestOf( - bracket: Bracket -): EliminationBracket< +export function participantCountToRoundsInfo({ + bracket, + mapPool, +}: { + bracket: Bracket; + mapPool: Stage[]; +}): EliminationBracket< { name: string; bestOf: number; + mapList: Stage[]; }[] > { - const { winners: winnersRoundNames, losers: losersRoundNames } = - roundNames(bracket); - const { winners: winnersDefaultBestOf, losers: losersDefaultBestOf } = - roundDefaultBestOf(bracket); + const roundNames = getRoundNames(bracket); + const roundsDefaultBestOf = getRoundsDefaultBestOf(bracket); + const mapList = generateMapListMapForRounds({ + mapPool, + rounds: roundsDefaultBestOf, + }); - invariant( - winnersRoundNames.length === winnersDefaultBestOf.length, - "Unexpected different length with winnersRoundNames and winnersDefaultBestOf" - ); - invariant( - losersRoundNames.length === losersDefaultBestOf.length, - "Unexpected different length with losersRoundNames and losersDefaultBestOf" - ); + // TODO: invariants return { - winners: winnersRoundNames.map((roundName, i) => { - const bestOf = winnersDefaultBestOf[i]; + winners: roundNames.winners.map((roundName, i) => { + const bestOf = roundsDefaultBestOf.winners[i]; + const maps = mapList.winners[i]; return { name: roundName, bestOf, + mapList: maps, }; }), - losers: losersRoundNames.map((roundName, i) => { - const bestOf = losersDefaultBestOf[i]; + losers: roundNames.losers.map((roundName, i) => { + const bestOf = roundsDefaultBestOf.losers[i]; + const maps = mapList.losers[i]; return { name: roundName, bestOf, + mapList: maps, }; }), }; @@ -64,7 +53,9 @@ const GRAND_FINALS_DEFAULT = 7; const LOSERS_DEFAULT = 3; const LOSERS_FINALS_DEFAULT = 5; -function roundDefaultBestOf(bracket: Bracket): EliminationBracket { +function getRoundsDefaultBestOf( + bracket: Bracket +): EliminationBracket { const { winners: winnersRoundCount, losers: losersRoundCount } = countRounds(bracket); @@ -83,7 +74,7 @@ function roundDefaultBestOf(bracket: Bracket): EliminationBracket { }; } -function roundNames(bracket: Bracket): EliminationBracket { +function getRoundNames(bracket: Bracket): EliminationBracket { const { winners: winnersRoundCount, losers: losersRoundCount } = countRounds(bracket); @@ -159,26 +150,6 @@ export function resolveTournamentFormatString( : "Single Elimination"; } -export type TeamIdentifier = number | "BYE"; - -export interface Match { - id: string; - upperTeam?: TeamIdentifier; - lowerTeam?: TeamIdentifier; - winner?: TeamIdentifier; - /** Match that leads to this match */ - match1?: Match; - /** Match that leads to this match */ - match2?: Match; -} - -export interface Bracket { - winners: Match[]; - losers: Match[]; - participantCount: number; - participantsWithByesCount: number; -} - export type EliminationBracket = { winners: T; losers: T; diff --git a/app/hooks/useTournamentRounds/index.ts b/app/hooks/useTournamentRounds/index.ts new file mode 100644 index 000000000..8ffe59840 --- /dev/null +++ b/app/hooks/useTournamentRounds/index.ts @@ -0,0 +1,35 @@ +import { eliminationBracket } from "~/core/tournament/algorithms"; +import { participantCountToRoundsInfo } from "~/core/tournament/bracket"; +import { + UseTournamentRoundsAction, + UseTournamentRoundsState, + UseTournamentsArgs, +} from "./types"; +import * as React from "react"; + +const getInitialState = ({ + teams, + type, + mapPool, +}: UseTournamentsArgs): UseTournamentRoundsState => { + const teamCount = teams.reduce( + (acc, cur) => acc + (cur.checkedInTime ? 1 : 0), + 0 + ); + const bracket = eliminationBracket(teamCount, type); + return participantCountToRoundsInfo({ bracket, mapPool }); +}; + +export function useTournamentRounds(args: UseTournamentsArgs) { + return React.useReducer( + (oldState: UseTournamentRoundsState, action: UseTournamentRoundsAction) => { + switch (action.type) { + case "TODO": + return oldState; + default: + return oldState; + } + }, + getInitialState(args) + ); +} diff --git a/app/hooks/useTournamentRounds/types.ts b/app/hooks/useTournamentRounds/types.ts new file mode 100644 index 000000000..65ee7c1e1 --- /dev/null +++ b/app/hooks/useTournamentRounds/types.ts @@ -0,0 +1,34 @@ +import type { BracketType, Stage } from ".prisma/client"; +import type { EliminationBracket } from "~/core/tournament/bracket"; + +export interface UseTournamentsArgs { + teams: { checkedInTime: string | null }[]; + type: BracketType; + mapPool: Stage[]; +} + +export type UseTournamentRoundsState = EliminationBracket< + { + bestOf: number; + name: string; + mapList: Stage[]; + }[] +>; + +export type UseTournamentRoundsAction = { type: "TODO" }; + +// export type Action = +// | { +// type: "SET_PLAYER"; +// name: string; +// number: number; +// } +// | { type: "CREATE_FIRST_MATCH" } +// | { type: "SET_WINNER"; winner: "alpha" | "bravo" } +// | { +// type: "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS"; +// amountOfRoundsWithSameTeams: number; +// } +// | { type: "UNDO_LATEST_MATCH" } +// | { type: "RESET" } +// | { type: "SET_NO_PLACING_TO_SAME_TEAM"; id: number; checked: boolean }; diff --git a/app/routes/to/$organization.$tournament/start.tsx b/app/routes/to/$organization.$tournament/start.tsx index 959d437ac..8e4ea4934 100644 --- a/app/routes/to/$organization.$tournament/start.tsx +++ b/app/routes/to/$organization.$tournament/start.tsx @@ -1,23 +1,55 @@ import { useMatches } from "remix"; -import { eliminationBracket } from "~/core/tournament/bracket"; -import { roundNamesWithDefaultBestOf } from "~/core/tournament/utils"; +import { useTournamentRounds } from "~/hooks/useTournamentRounds"; import type { FindTournamentByNameForUrlI } from "~/services/tournament"; +// TODO: handle warning if check-in has not concluded export default function StartBracketTab() { const [, parentRoute] = useMatches(); - const { teams } = parentRoute.data as FindTournamentByNameForUrlI; + const { teams, mapPool } = parentRoute.data as FindTournamentByNameForUrlI; + const [rounds, dispatch] = useTournamentRounds({ + mapPool, + teams, + type: "DE", + }); - const teamCount = teams.reduce( - (acc, cur) => acc + (cur.checkedInTime ? 1 : 0), - 0 - ); return ( -
-      {JSON.stringify(
-        roundNamesWithDefaultBestOf(eliminationBracket(teamCount, "DE")),
-        null,
-        2
-      )}
-    
+
+

Winners

+ {rounds.winners.map((round) => { + return ( + // TODO: key potentially unstable +
+

{round.name}

+
    + {round.mapList.map((stage) => { + return ( +
  1. + {stage.name} {stage.mode} +
  2. + ); + })} +
+
+ ); + })} +

Losers

+ {rounds.losers.map((round) => { + return ( + // TODO: key potentially unstable +
+

{round.name}

+
    + {round.mapList.map((stage) => { + return ( +
  1. + {stage.name} {stage.mode} +
  2. + ); + })} +
+
+ ); + })} +
); } diff --git a/app/services/tournament.prisma.ts b/app/services/tournament.prisma.ts index 3d93421f3..74463cfb2 100644 --- a/app/services/tournament.prisma.ts +++ b/app/services/tournament.prisma.ts @@ -30,6 +30,7 @@ export function tournamentsByNameForUrl(tournamentNameForUrl: string) { }, mapPool: { select: { + id: true, mode: true, name: true, },