mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 15:17:43 -05:00
Start page UI hooks skeleton
This commit is contained in:
@@ -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
31
app/core/mapList.ts
Normal 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" }));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
35
app/hooks/useTournamentRounds/index.ts
Normal file
35
app/hooks/useTournamentRounds/index.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
34
app/hooks/useTournamentRounds/types.ts
Normal file
34
app/hooks/useTournamentRounds/types.ts
Normal 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 };
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export function tournamentsByNameForUrl(tournamentNameForUrl: string) {
|
||||
},
|
||||
mapPool: {
|
||||
select: {
|
||||
id: true,
|
||||
mode: true,
|
||||
name: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user