From a6a303302c8fd5c4711d36ce519919dcd3bc214b Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Fri, 17 Dec 2021 09:17:58 +0200 Subject: [PATCH] Can change best of and regen maps --- app/core/tournament/bracket.ts | 4 +- app/hooks/useTournamentRounds/index.ts | 42 ++++++++++-- app/hooks/useTournamentRounds/types.ts | 19 +++++- .../to/$organization.$tournament/start.tsx | 68 +++++++++++++++---- app/styles/tournament-start.css | 21 +++++- app/utils/index.ts | 5 ++ 6 files changed, 133 insertions(+), 26 deletions(-) diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts index 7e5c2df68..2fd6481ad 100644 --- a/app/core/tournament/bracket.ts +++ b/app/core/tournament/bracket.ts @@ -12,7 +12,7 @@ export function participantCountToRoundsInfo({ }): EliminationBracket< { name: string; - bestOf: number; + bestOf: BestOf; mapList: Stage[]; }[] > { @@ -160,3 +160,5 @@ export type EliminationBracket = { winners: T; losers: T; }; + +export type EliminationBracketSide = "winners" | "losers"; diff --git a/app/hooks/useTournamentRounds/index.ts b/app/hooks/useTournamentRounds/index.ts index 15bfe46b8..230f20b1e 100644 --- a/app/hooks/useTournamentRounds/index.ts +++ b/app/hooks/useTournamentRounds/index.ts @@ -1,16 +1,48 @@ import * as React from "react"; -import { UseTournamentRoundsAction, UseTournamentRoundsState } from "./types"; +import { generateMapListForRounds } from "~/core/tournament/mapList"; +import type { + UseTournamentRoundsAction, + UseTournamentRoundsArgs, + UseTournamentRoundsState, +} from "./types"; -export function useTournamentRounds(initialState: UseTournamentRoundsState) { +export function useTournamentRounds(args: UseTournamentRoundsArgs) { return React.useReducer( (oldState: UseTournamentRoundsState, action: UseTournamentRoundsAction) => { switch (action.type) { - case "TODO": - return oldState; + case "SET_ROUND_BEST_OF": + const newState = { ...oldState }; + newState[action.data.side][action.data.index].bestOf = + action.data.newBestOf; + + return regenMapList(newState); default: return oldState; } }, - initialState + args.initialState ); + + function regenMapList( + state: UseTournamentRoundsState + ): UseTournamentRoundsState { + const newMapLists = generateMapListForRounds({ + mapPool: args.mapPool, + rounds: { + winners: state.winners.map((r) => r.bestOf), + losers: state.losers.map((r) => r.bestOf), + }, + }); + + return { + winners: state.winners.map((round, i) => ({ + ...round, + mapList: newMapLists.winners[i], + })), + losers: state.losers.map((round, i) => ({ + ...round, + mapList: newMapLists.losers[i], + })), + }; + } } diff --git a/app/hooks/useTournamentRounds/types.ts b/app/hooks/useTournamentRounds/types.ts index e749398e3..6122a3571 100644 --- a/app/hooks/useTournamentRounds/types.ts +++ b/app/hooks/useTournamentRounds/types.ts @@ -1,15 +1,28 @@ import type { Stage } from ".prisma/client"; -import type { EliminationBracket } from "~/core/tournament/bracket"; +import type { + BestOf, + EliminationBracket, + EliminationBracketSide, +} from "~/core/tournament/bracket"; +import { MyReducerAction } from "~/utils"; export type UseTournamentRoundsState = EliminationBracket< { - bestOf: number; + bestOf: BestOf; name: string; mapList: Stage[]; }[] >; -export type UseTournamentRoundsAction = { type: "TODO" }; +export interface UseTournamentRoundsArgs { + initialState: UseTournamentRoundsState; + mapPool: Stage[]; +} + +export type UseTournamentRoundsAction = MyReducerAction< + "SET_ROUND_BEST_OF", + { newBestOf: BestOf; side: EliminationBracketSide; index: number } +>; // export type Action = // | { diff --git a/app/routes/to/$organization.$tournament/start.tsx b/app/routes/to/$organization.$tournament/start.tsx index db88c12a1..dc6e1e7ba 100644 --- a/app/routes/to/$organization.$tournament/start.tsx +++ b/app/routes/to/$organization.$tournament/start.tsx @@ -1,14 +1,22 @@ +import classNames from "classnames"; import type { LinksFunction, LoaderFunction } from "remix"; import { json, useLoaderData } from "remix"; import invariant from "tiny-invariant"; import { eliminationBracket } from "~/core/tournament/algorithms"; -import { participantCountToRoundsInfo } from "~/core/tournament/bracket"; +import { + EliminationBracketSide, + participantCountToRoundsInfo, +} from "~/core/tournament/bracket"; import { useTournamentRounds } from "~/hooks/useTournamentRounds"; -import type { UseTournamentRoundsState } from "~/hooks/useTournamentRounds/types"; +import type { + UseTournamentRoundsAction, + UseTournamentRoundsArgs, + UseTournamentRoundsState, +} from "~/hooks/useTournamentRounds/types"; import { findTournamentByNameForUrl } from "~/services/tournament"; import startBracketTabStylesUrl from "~/styles/tournament-start.css"; -// 2) can change best of -> regens maps +// 1) jank // 3) can regen maps via button // 4) can change any invidual map via dropdown (no regen) // 5) Blackbelly TC repeats Losers Round 3 and 4 -> add test & fix? (use real maps as test data) @@ -17,7 +25,7 @@ export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: startBracketTabStylesUrl }]; }; -const typedJson = (args: UseTournamentRoundsState) => json(args); +const typedJson = (args: UseTournamentRoundsArgs) => json(args); export const loader: LoaderFunction = async ({ params }) => { invariant( @@ -41,23 +49,31 @@ export const loader: LoaderFunction = async ({ params }) => { // TODO: and also below don't show losers if SE const bracket = eliminationBracket(teamCount, "DE"); - const result = participantCountToRoundsInfo({ + const initialState = participantCountToRoundsInfo({ bracket, mapPool: tournament.mapPool, }); - return typedJson(result); + return typedJson({ initialState, mapPool: tournament.mapPool }); }; // TODO: handle warning if check-in has not concluded export default function StartBracketTab() { - const initialState = useLoaderData(); - const [rounds, dispatch] = useTournamentRounds(initialState); + const args = useLoaderData(); + const [rounds, dispatch] = useTournamentRounds(args); return (
- - + +
); } @@ -65,21 +81,42 @@ export default function StartBracketTab() { function RoundsCollection({ side, rounds, + dispatch, }: { - side: "Winners" | "Losers"; + side: EliminationBracketSide; rounds: UseTournamentRoundsState["winners"]; + dispatch: React.Dispatch; }) { return ( <> -

{side}

+

{side}

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

{round.name}

-
- Best of {round.bestOf} +
+ {([3, 5, 7, 9] as const).map((bestOf) => ( + + ))}
    {round.mapList.map((stage) => { @@ -88,6 +125,7 @@ function RoundsCollection({ {" "} {stage.name} diff --git a/app/styles/tournament-start.css b/app/styles/tournament-start.css index 53d316a1c..b03b581b2 100644 --- a/app/styles/tournament-start.css +++ b/app/styles/tournament-start.css @@ -1,3 +1,7 @@ +.tournament__start__title { + text-transform: capitalize; +} + .tournament__start__container { width: 100%; } @@ -14,12 +18,25 @@ border-radius: var(--rounded); } +.tournament__start__best-of-buttons-container { + display: flex; + gap: var(--s-4); +} + .tournament__start__best-of { - color: var(--theme-secondary); + padding: 0; + border-color: transparent; + background-color: transparent; + color: var(--text-lighter); font-size: var(--fonts-xs); font-weight: var(--semi-bold); } +.tournament__start__best-of.active { + color: var(--theme-secondary); + font-weight: var(--bold); +} + .tournament__start__rounds-list { padding-top: var(--s-2); padding-left: 0; @@ -34,5 +51,5 @@ } .tournament__start__mode-image { - width: 1.75rem; + width: attr(width); } diff --git a/app/utils/index.ts b/app/utils/index.ts index 79f281b20..ff75bcbec 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -66,5 +66,10 @@ export type Unpacked = T extends (infer U)[] ? U : T; +export type MyReducerAction = { + type: K; + data: T; +}; + // TODO: // export type InferredSerializedAPI = Serialized>;