mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 07:32:19 -05:00
Create tournaments rounds seems to work edition
This commit is contained in:
parent
88dc19f450
commit
d0b9708510
|
|
@ -24,16 +24,22 @@ export const stages = [
|
|||
"New Albacore Hotel",
|
||||
"Ancho-V Games",
|
||||
"Skipper Pavilion",
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const modesShort: Mode[] = ["TW", "SZ", "TC", "RM", "CB"];
|
||||
export const modesShort: readonly Mode[] = [
|
||||
"TW",
|
||||
"SZ",
|
||||
"TC",
|
||||
"RM",
|
||||
"CB",
|
||||
] as const;
|
||||
export const modesShortToLong: Record<Mode, string> = {
|
||||
TW: "Turf War",
|
||||
SZ: "Splat Zones",
|
||||
TC: "Tower Control",
|
||||
RM: "Rainmaker",
|
||||
CB: "Clam Blitz",
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const navItems = [
|
||||
{
|
||||
|
|
@ -52,7 +58,7 @@ export const navItems = [
|
|||
title: "misc",
|
||||
items: ["badges", "links"],
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const DISCORD_URL = "https://discord.gg/sendou";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { BracketType, Stage, TeamOrder } from ".prisma/client";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import invariant from "tiny-invariant";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
import { TOURNAMENT_TEAM_ROSTER_MIN_SIZE } from "../../constants";
|
||||
import { FindTournamentByNameForUrlI } from "../../services/tournament";
|
||||
import { Bracket, eliminationBracket, Match } from "./algorithms";
|
||||
|
|
@ -183,6 +184,12 @@ export function countParticipants(teams: FindTournamentByNameForUrlI["teams"]) {
|
|||
}, 0);
|
||||
}
|
||||
|
||||
export type MapListIds = z.infer<typeof MapListIdsSchema>;
|
||||
export const MapListIdsSchema = z.object({
|
||||
winners: z.array(z.array(z.object({ id: z.number() }))),
|
||||
losers: z.array(z.array(z.object({ id: z.number() }))),
|
||||
});
|
||||
|
||||
export interface TournamentRoundForDB {
|
||||
id: string;
|
||||
position: number;
|
||||
|
|
@ -205,7 +212,7 @@ export function tournamentRoundsForDB({
|
|||
bracketType,
|
||||
participantsSeeded,
|
||||
}: {
|
||||
mapList: EliminationBracket<Stage[][]>;
|
||||
mapList: MapListIds;
|
||||
bracketType: BracketType;
|
||||
participantsSeeded: { id: string }[];
|
||||
}): TournamentRoundForDB[] {
|
||||
|
|
@ -221,7 +228,6 @@ export function tournamentRoundsForDB({
|
|||
const isWinners = sideI === 0;
|
||||
for (const [roundI, round] of side.entries()) {
|
||||
const position = isWinners ? roundI + 1 : -(roundI + 1);
|
||||
|
||||
const stagesRaw = mapList[isWinners ? "winners" : "losers"][roundI];
|
||||
invariant(stagesRaw, "stagesRaw is undefined");
|
||||
const stages = stagesRaw.map((stage, i) => ({
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export function CatchBoundary() {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO.... maybe we can render the same exact thing with added dialog you can close?
|
||||
export function ErrorBoundary({ error }: { error: Error }) {
|
||||
console.error(error);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ import type { Mode, Stage } from ".prisma/client";
|
|||
import classNames from "classnames";
|
||||
import {
|
||||
ActionFunction,
|
||||
Form,
|
||||
json,
|
||||
LinksFunction,
|
||||
LoaderFunction,
|
||||
redirect,
|
||||
useLoaderData,
|
||||
useMatches,
|
||||
} from "remix";
|
||||
import { Form, json, redirect, useLoaderData } from "remix";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Alert } from "~/components/Alert";
|
||||
import { Button } from "~/components/Button";
|
||||
|
|
@ -14,8 +17,9 @@ import { Catcher } from "~/components/Catcher";
|
|||
import { modesShort, modesShortToLong } from "~/constants";
|
||||
import { eliminationBracket } from "~/core/tournament/algorithms";
|
||||
import {
|
||||
EliminationBracket,
|
||||
EliminationBracketSide,
|
||||
MapListIds,
|
||||
MapListIdsSchema,
|
||||
participantCountToRoundsInfo,
|
||||
} from "~/core/tournament/bracket";
|
||||
import { useTournamentRounds } from "~/hooks/useTournamentRounds";
|
||||
|
|
@ -44,8 +48,7 @@ export const action: ActionFunction = async ({ request, params, context }) => {
|
|||
const bracketId = formData.get("bracket-id");
|
||||
invariant(typeof mapListString === "string", "Type of map list not string");
|
||||
invariant(typeof bracketId === "string", "Type of bracket id not string");
|
||||
// TODO: could use Zod here
|
||||
const mapList = JSON.parse(mapListString) as EliminationBracket<Stage[][]>;
|
||||
const mapList = MapListIdsSchema.parse(JSON.parse(mapListString));
|
||||
|
||||
const organizationNameForUrl = params.organization;
|
||||
const tournamentNameForUrl = params.tournament;
|
||||
|
|
@ -111,15 +114,17 @@ export default function StartBracketTab() {
|
|||
// TODO: dropdown to select this
|
||||
const bracketId = brackets[0].id;
|
||||
|
||||
const mapListForInput: MapListIds = {
|
||||
losers: bracket.winners.map((round) => round.mapList),
|
||||
winners: bracket.winners.map((round) => round.mapList),
|
||||
};
|
||||
|
||||
return (
|
||||
<Form method="post" className="width-100">
|
||||
<input
|
||||
type="hidden"
|
||||
name="map-list"
|
||||
value={JSON.stringify({
|
||||
losers: bracket.winners.map((round) => round.mapList),
|
||||
winners: bracket.winners.map((round) => round.mapList),
|
||||
})}
|
||||
value={JSON.stringify(mapListForInput)}
|
||||
/>
|
||||
<input type="hidden" name="bracket-id" value={bracketId} />
|
||||
<div className="tournament__start__container">
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import type { Prisma, Stage } from ".prisma/client";
|
||||
import type { Prisma } from ".prisma/client";
|
||||
import {
|
||||
TOURNAMENT_CHECK_IN_CLOSING_MINUTES_FROM_START,
|
||||
TOURNAMENT_TEAM_ROSTER_MAX_SIZE,
|
||||
} from "~/constants";
|
||||
import {
|
||||
EliminationBracket,
|
||||
tournamentRoundsForDB,
|
||||
} from "~/core/tournament/bracket";
|
||||
import { MapListIds, tournamentRoundsForDB } from "~/core/tournament/bracket";
|
||||
import { isTournamentAdmin } from "~/core/tournament/permissions";
|
||||
import { sortTeamsBySeed } from "~/core/tournament/utils";
|
||||
import * as Tournament from "~/models/Tournament";
|
||||
|
|
@ -134,7 +131,7 @@ export async function createTournamentRounds({
|
|||
}: {
|
||||
organizationNameForUrl: string;
|
||||
tournamentNameForUrl: string;
|
||||
mapList: EliminationBracket<Stage[][]>;
|
||||
mapList: MapListIds;
|
||||
userId: string;
|
||||
bracketId: string;
|
||||
}) {
|
||||
|
|
@ -156,9 +153,9 @@ export async function createTournamentRounds({
|
|||
throw new Response("Invalid bracket id provided", { status: 400 });
|
||||
}
|
||||
|
||||
const participantsSeeded = tournament.teams.sort(
|
||||
sortTeamsBySeed(tournament.seeds)
|
||||
);
|
||||
const participantsSeeded = tournament.teams
|
||||
.filter((team) => team.checkedInTime)
|
||||
.sort(sortTeamsBySeed(tournament.seeds));
|
||||
|
||||
const rounds = tournamentRoundsForDB({
|
||||
mapList,
|
||||
|
|
@ -176,13 +173,17 @@ export async function createTournamentRounds({
|
|||
}),
|
||||
db.tournamentRoundStage.createMany({
|
||||
data: rounds.flatMap((round) => {
|
||||
return round.stages.map((stage) => ({ ...stage, roundId: round.id }));
|
||||
return round.stages.map(({ position, stageId }) => ({
|
||||
position,
|
||||
stageId,
|
||||
roundId: round.id,
|
||||
}));
|
||||
}),
|
||||
}),
|
||||
db.tournamentMatch.createMany({
|
||||
data: rounds.flatMap((round) => {
|
||||
return round.matches.map((match) => ({
|
||||
...match,
|
||||
id: match.id,
|
||||
roundId: round.id,
|
||||
}));
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as React from "react";
|
|||
export const useUser = () => {
|
||||
const [root] = useMatches();
|
||||
|
||||
return root.data.user as LoggedInUser;
|
||||
return root.data?.user as LoggedInUser;
|
||||
};
|
||||
|
||||
export const useBaseURL = () => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ export async function formDataFromRequest<T extends string>({
|
|||
return result as Record<T, string>;
|
||||
}
|
||||
|
||||
/** @link https://stackoverflow.com/a/69413184 */
|
||||
// @ts-expect-error
|
||||
export const assertType = <A, B extends A>() => {};
|
||||
|
||||
export type LoggedInUser = {
|
||||
id: string;
|
||||
discordId: string;
|
||||
|
|
|
|||
8
app/validators/mode.ts
Normal file
8
app/validators/mode.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Mode } from ".prisma/client";
|
||||
import { z } from "zod";
|
||||
import { assertType, Unpacked } from "~/utils";
|
||||
|
||||
type MapList = z.infer<typeof ModeSchema>;
|
||||
assertType<Unpacked<MapList>, Mode>();
|
||||
|
||||
export const ModeSchema = z.enum(["TW", "SZ", "TC", "RM", "CB"]);
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -28,7 +28,8 @@
|
|||
"react-dom": "^17.0.2",
|
||||
"remix": "^1.1.1",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"uuid": "^8.3.2"
|
||||
"uuid": "^8.3.2",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.1.1",
|
||||
|
|
@ -9106,6 +9107,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.11.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz",
|
||||
"integrity": "sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
|
|
@ -15782,6 +15791,11 @@
|
|||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"zod": {
|
||||
"version": "3.11.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz",
|
||||
"integrity": "sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg=="
|
||||
},
|
||||
"zwitch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@
|
|||
"react-dom": "^17.0.2",
|
||||
"remix": "^1.1.1",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"uuid": "^8.3.2"
|
||||
"uuid": "^8.3.2",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.1.1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user