mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-10 21:26:08 -05:00
Can change best of and regen maps
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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],
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
// | {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
Reference in New Issue
Block a user