Start page UI hooks skeleton

This commit is contained in:
Kalle (Sendou)
2021-12-15 09:26:55 +02:00
parent 9addeb0e26
commit 1398dadc31
8 changed files with 198 additions and 75 deletions

View File

@@ -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";

31
app/core/mapList.ts Normal file
View File

@@ -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<number[]>;
}): EliminationBracket<Stage[][]> {
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" }));
}
}

View File

@@ -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, "id">): 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;
}

View File

@@ -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<number[]> {
function getRoundsDefaultBestOf(
bracket: Bracket
): EliminationBracket<number[]> {
const { winners: winnersRoundCount, losers: losersRoundCount } =
countRounds(bracket);
@@ -83,7 +74,7 @@ function roundDefaultBestOf(bracket: Bracket): EliminationBracket<number[]> {
};
}
function roundNames(bracket: Bracket): EliminationBracket<string[]> {
function getRoundNames(bracket: Bracket): EliminationBracket<string[]> {
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<T> = {
winners: T;
losers: T;

View File

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

View File

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

View File

@@ -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 (
<pre>
{JSON.stringify(
roundNamesWithDefaultBestOf(eliminationBracket(teamCount, "DE")),
null,
2
)}
</pre>
<div>
<h2>Winners</h2>
{rounds.winners.map((round) => {
return (
// TODO: key potentially unstable
<section key={round.name}>
<h3>{round.name}</h3>
<ol>
{round.mapList.map((stage) => {
return (
<li key={stage.id}>
{stage.name} {stage.mode}
</li>
);
})}
</ol>
</section>
);
})}
<h2>Losers</h2>
{rounds.losers.map((round) => {
return (
// TODO: key potentially unstable
<section key={round.name}>
<h3>{round.name}</h3>
<ol>
{round.mapList.map((stage) => {
return (
<li key={stage.id}>
{stage.name} {stage.mode}
</li>
);
})}
</ol>
</section>
);
})}
</div>
);
}

View File

@@ -30,6 +30,7 @@ export function tournamentsByNameForUrl(tournamentNameForUrl: string) {
},
mapPool: {
select: {
id: true,
mode: true,
name: true,
},