Can change best of and regen maps

This commit is contained in:
Kalle (Sendou)
2021-12-17 09:17:58 +02:00
parent 357f8b1024
commit a6a303302c
6 changed files with 133 additions and 26 deletions

View File

@@ -12,7 +12,7 @@ export function participantCountToRoundsInfo({
}): EliminationBracket<
{
name: string;
bestOf: number;
bestOf: BestOf;
mapList: Stage[];
}[]
> {
@@ -160,3 +160,5 @@ export type EliminationBracket<T> = {
winners: T;
losers: T;
};
export type EliminationBracketSide = "winners" | "losers";

View File

@@ -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],
})),
};
}
}

View File

@@ -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 =
// | {

View File

@@ -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<UseTournamentRoundsState>();
const [rounds, dispatch] = useTournamentRounds(initialState);
const args = useLoaderData<UseTournamentRoundsArgs>();
const [rounds, dispatch] = useTournamentRounds(args);
return (
<div className="tournament__start__container">
<RoundsCollection side="Winners" rounds={rounds.winners} />
<RoundsCollection side="Losers" rounds={rounds.losers} />
<RoundsCollection
side="winners"
rounds={rounds.winners}
dispatch={dispatch}
/>
<RoundsCollection
side="losers"
rounds={rounds.losers}
dispatch={dispatch}
/>
</div>
);
}
@@ -65,21 +81,42 @@ export default function StartBracketTab() {
function RoundsCollection({
side,
rounds,
dispatch,
}: {
side: "Winners" | "Losers";
side: EliminationBracketSide;
rounds: UseTournamentRoundsState["winners"];
dispatch: React.Dispatch<UseTournamentRoundsAction>;
}) {
return (
<>
<h2>{side}</h2>
<h2 className="tournament__start__title">{side}</h2>
<div className="tournament__start__rounds-container">
{rounds.map((round) => {
{rounds.map((round, roundIndex) => {
return (
// TODO: key potentially unstable
<section key={round.name} className="tournament__start__round">
<h4>{round.name}</h4>
<div className="tournament__start__best-of">
Best of {round.bestOf}
<div className="tournament__start__best-of-buttons-container">
{([3, 5, 7, 9] as const).map((bestOf) => (
<button
key={bestOf}
className={classNames("tournament__start__best-of", {
active: round.bestOf === bestOf,
})}
onClick={() =>
dispatch({
type: "SET_ROUND_BEST_OF",
data: {
newBestOf: bestOf,
side,
index: roundIndex,
},
})
}
>
Bo{bestOf}
</button>
))}
</div>
<ol className="tournament__start__rounds-list">
{round.mapList.map((stage) => {
@@ -88,6 +125,7 @@ function RoundsCollection({
<img
src={`/img/modes/${stage.mode}.webp`}
className="tournament__start__mode-image"
width="30"
/>{" "}
<span>{stage.name}</span>
</li>

View File

@@ -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);
}

View File

@@ -66,5 +66,10 @@ export type Unpacked<T> = T extends (infer U)[]
? U
: T;
export type MyReducerAction<K extends string, T extends {}> = {
type: K;
data: T;
};
// TODO:
// export type InferredSerializedAPI<T> = Serialized<Prisma.PromiseReturnType<T>>;