mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-12 14:16:04 -05:00
Report score works without advance
This commit is contained in:
@@ -43,7 +43,11 @@ export function DuringMatchActions({
|
||||
});
|
||||
|
||||
if (joinedRoom) {
|
||||
const currentStage = currentRound.stages.find((m) => m.position === 1);
|
||||
const currentPosition =
|
||||
currentMatch.score?.reduce((acc, cur) => acc + cur, 1) ?? 1;
|
||||
const currentStage = currentRound.stages.find(
|
||||
(m) => m.position === currentPosition
|
||||
);
|
||||
invariant(currentStage, "currentStage is undefined");
|
||||
const { stage } = currentStage;
|
||||
|
||||
@@ -66,9 +70,7 @@ export function DuringMatchActions({
|
||||
/>
|
||||
{modesShortToLong[stage.mode]} on {stage.name}
|
||||
</h4>
|
||||
<h4>
|
||||
Stage {currentMatch.score?.reduce((acc, cur) => acc + cur, 1)}
|
||||
</h4>
|
||||
<h4>Stage {currentPosition}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<ActionSectionWrapper justify-center>
|
||||
@@ -90,6 +92,7 @@ export function DuringMatchActions({
|
||||
ownTeam={ownTeam}
|
||||
opponentTeam={opponentTeam}
|
||||
matchId={currentMatch.id}
|
||||
position={currentPosition}
|
||||
/>
|
||||
</ActionSectionWrapper>
|
||||
</div>
|
||||
|
||||
@@ -11,10 +11,12 @@ export function DuringMatchActionsRosters({
|
||||
ownTeam,
|
||||
opponentTeam,
|
||||
matchId,
|
||||
position,
|
||||
}: {
|
||||
ownTeam: Unpacked<FindTournamentByNameForUrlI["teams"]>;
|
||||
opponentTeam: Unpacked<FindTournamentByNameForUrlI["teams"]>;
|
||||
matchId: string;
|
||||
position: number;
|
||||
}) {
|
||||
const [checkedPlayers, setCheckedPlayers] = React.useState<
|
||||
[string[], string[]]
|
||||
@@ -89,6 +91,7 @@ export function DuringMatchActionsRosters({
|
||||
name="playerIds"
|
||||
value={JSON.stringify(checkedPlayers.flat())}
|
||||
/>
|
||||
<input type="hidden" name="position" value={position} />
|
||||
<ReportScoreButtons
|
||||
checkedPlayers={checkedPlayers}
|
||||
winnerName={winningTeam()}
|
||||
|
||||
@@ -74,6 +74,8 @@ export const TOURNAMENT_TEAM_ROSTER_MIN_SIZE = 4;
|
||||
export const TOURNAMENT_TEAM_ROSTER_MAX_SIZE = 6;
|
||||
/** How many minutes before the start of the tournament check-in closes */
|
||||
export const TOURNAMENT_CHECK_IN_CLOSING_MINUTES_FROM_START = 10;
|
||||
export const BEST_OF_OPTIONS = [3, 5, 7, 9] as const;
|
||||
|
||||
export const checkInClosesDate = (startTime: string): Date => {
|
||||
return new Date(new Date(startTime).getTime() - 1000 * 10);
|
||||
};
|
||||
|
||||
@@ -26,13 +26,13 @@ export function findById(id: string) {
|
||||
}
|
||||
|
||||
export function createResult({
|
||||
position,
|
||||
roundStageId,
|
||||
reporterId,
|
||||
winner,
|
||||
matchId,
|
||||
playerIds,
|
||||
}: {
|
||||
position: number;
|
||||
roundStageId: string;
|
||||
reporterId: string;
|
||||
winner: TeamOrder;
|
||||
matchId: string;
|
||||
@@ -40,7 +40,11 @@ export function createResult({
|
||||
}) {
|
||||
return db.tournamentMatchGameResult.create({
|
||||
data: {
|
||||
position,
|
||||
roundStage: {
|
||||
connect: {
|
||||
id: roundStageId,
|
||||
},
|
||||
},
|
||||
reporterId,
|
||||
winner,
|
||||
players: {
|
||||
|
||||
@@ -12,7 +12,8 @@ import type { BracketModified } from "~/services/tournament";
|
||||
import { useUser } from "~/utils/hooks";
|
||||
import { BracketActions } from "~/components/tournament/BracketActions";
|
||||
import { z } from "zod";
|
||||
import { parseRequestFormData, requireUser } from "~/utils";
|
||||
import { parseRequestFormData, requireUser, safeJSONParse } from "~/utils";
|
||||
import { BEST_OF_OPTIONS, TOURNAMENT_TEAM_ROSTER_MIN_SIZE } from "~/constants";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
@@ -23,9 +24,16 @@ const bracketActionSchema = z.union([
|
||||
_action: z.literal("REPORT_SCORE"),
|
||||
matchId: z.string().uuid(),
|
||||
winnerTeamId: z.string().uuid(),
|
||||
position: z.preprocess(
|
||||
Number,
|
||||
z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(Math.max(...BEST_OF_OPTIONS))
|
||||
),
|
||||
playerIds: z.preprocess(
|
||||
(val) => (val ? JSON.parse(val as any) : undefined),
|
||||
z.array(z.string().uuid()).length(8)
|
||||
safeJSONParse,
|
||||
z.array(z.string().uuid()).length(TOURNAMENT_TEAM_ROSTER_MIN_SIZE * 2)
|
||||
),
|
||||
}),
|
||||
z.object({
|
||||
@@ -55,6 +63,7 @@ export const action: ActionFunction = async ({
|
||||
playerIds: data.playerIds,
|
||||
userId: user.id,
|
||||
winnerTeamId: data.winnerTeamId,
|
||||
position: data.position,
|
||||
});
|
||||
return { ok: "REPORT_SCORE" };
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Alert } from "~/components/Alert";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Catcher } from "~/components/Catcher";
|
||||
import { RefreshIcon } from "~/components/icons/Refresh";
|
||||
import { modesShort, modesShortToLong } from "~/constants";
|
||||
import { BEST_OF_OPTIONS, modesShort, modesShortToLong } from "~/constants";
|
||||
import { eliminationBracket } from "~/core/tournament/algorithms";
|
||||
import {
|
||||
EliminationBracketSide,
|
||||
@@ -229,7 +229,7 @@ function RoundsCollection({
|
||||
<section key={round.name} className="tournament__start__round">
|
||||
<h4>{round.name}</h4>
|
||||
<div className="tournament__start__best-of-buttons-container">
|
||||
{([3, 5, 7, 9] as const).map((bestOf) => (
|
||||
{BEST_OF_OPTIONS.map((bestOf) => (
|
||||
<button
|
||||
key={bestOf}
|
||||
type="button"
|
||||
|
||||
@@ -24,6 +24,7 @@ import * as TrustRelationship from "~/models/TrustRelationship";
|
||||
import * as TournamentMatch from "~/models/TournamentMatch";
|
||||
import { Serialized, Unpacked } from "~/utils";
|
||||
import { db } from "~/utils/db.server";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export type FindTournamentByNameForUrlI = Serialized<
|
||||
Prisma.PromiseReturnType<typeof findTournamentByNameForUrl>
|
||||
@@ -536,11 +537,13 @@ export async function reportScore({
|
||||
winnerTeamId,
|
||||
matchId,
|
||||
playerIds,
|
||||
position,
|
||||
}: {
|
||||
userId: string;
|
||||
winnerTeamId: string;
|
||||
matchId: string;
|
||||
playerIds: string[];
|
||||
position: number;
|
||||
}) {
|
||||
const match = await TournamentMatch.findById(matchId);
|
||||
if (!match) throw new Response("Invalid match id", { status: 400 });
|
||||
@@ -552,7 +555,8 @@ export async function reportScore({
|
||||
) {
|
||||
throw new Response("No permissions to report score", { status: 401 });
|
||||
}
|
||||
if (match.results.some((result) => result.matchId === matchId)) {
|
||||
|
||||
if (position <= match.results.length) {
|
||||
// no throw so it's handled gracefully if both teams report the score at the same time
|
||||
return;
|
||||
}
|
||||
@@ -567,11 +571,14 @@ export async function reportScore({
|
||||
throw new Response("Invalid winner team id", { status: 400 });
|
||||
}
|
||||
|
||||
const stage = match.round.stages.find((stage) => stage.position === position);
|
||||
invariant(stage, "stage is undefined");
|
||||
|
||||
// TODO transaction advance bracket conditionally
|
||||
return TournamentMatch.createResult({
|
||||
matchId,
|
||||
playerIds,
|
||||
position: match.position,
|
||||
roundStageId: stage.id,
|
||||
reporterId: userId,
|
||||
winner: winnerTeam.order,
|
||||
});
|
||||
|
||||
@@ -64,6 +64,14 @@ export async function parseRequestFormData<T extends z.ZodTypeAny>({
|
||||
}
|
||||
}
|
||||
|
||||
export function safeJSONParse(value: any) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/** @link https://stackoverflow.com/a/69413184 */
|
||||
// @ts-expect-error
|
||||
export const assertType = <A, B extends A>() => {};
|
||||
|
||||
@@ -112,9 +112,12 @@ CREATE TABLE "TournamentRound" (
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentRoundStage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"position" INTEGER NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"stageId" INTEGER NOT NULL
|
||||
"stageId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentRoundStage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
@@ -139,7 +142,7 @@ CREATE TABLE "TournamentMatchParticipant" (
|
||||
CREATE TABLE "TournamentMatchGameResult" (
|
||||
"id" TEXT NOT NULL,
|
||||
"matchId" TEXT NOT NULL,
|
||||
"position" INTEGER NOT NULL,
|
||||
"roundStageId" TEXT NOT NULL,
|
||||
"winner" "TeamOrder" NOT NULL,
|
||||
"reporterId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -187,7 +190,7 @@ CREATE UNIQUE INDEX "TournamentRoundStage_position_roundId_key" ON "TournamentRo
|
||||
CREATE UNIQUE INDEX "TournamentMatchParticipant_teamId_matchId_key" ON "TournamentMatchParticipant"("teamId", "matchId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TournamentMatchGameResult_matchId_position_key" ON "TournamentMatchGameResult"("matchId", "position");
|
||||
CREATE UNIQUE INDEX "TournamentMatchGameResult_matchId_roundStageId_key" ON "TournamentMatchGameResult"("matchId", "roundStageId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_TournamentMatchGameResultToUser_AB_unique" ON "_TournamentMatchGameResultToUser"("A", "B");
|
||||
@@ -252,6 +255,9 @@ ALTER TABLE "TournamentMatchParticipant" ADD CONSTRAINT "TournamentMatchParticip
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentMatchGameResult" ADD CONSTRAINT "TournamentMatchGameResult_matchId_fkey" FOREIGN KEY ("matchId") REFERENCES "TournamentMatch"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentMatchGameResult" ADD CONSTRAINT "TournamentMatchGameResult_roundStageId_fkey" FOREIGN KEY ("roundStageId") REFERENCES "TournamentRoundStage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_TournamentMatchGameResultToUser" ADD FOREIGN KEY ("A") REFERENCES "TournamentMatchGameResult"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -146,12 +146,14 @@ model TournamentRound {
|
||||
}
|
||||
|
||||
model TournamentRoundStage {
|
||||
id String @id @default(uuid())
|
||||
// position of the match 1 for Game 1, 2 for Game 2 etc.
|
||||
position Int
|
||||
roundId String
|
||||
stageId Int
|
||||
round TournamentRound @relation(fields: [roundId], references: [id])
|
||||
stage Stage @relation(fields: [stageId], references: [id])
|
||||
round TournamentRound @relation(fields: [roundId], references: [id])
|
||||
stage Stage @relation(fields: [stageId], references: [id])
|
||||
results TournamentMatchGameResult[]
|
||||
|
||||
@@unique([position, roundId])
|
||||
}
|
||||
@@ -188,14 +190,15 @@ model TournamentMatchParticipant {
|
||||
}
|
||||
|
||||
model TournamentMatchGameResult {
|
||||
id String @id @default(uuid())
|
||||
matchId String
|
||||
position Int
|
||||
winner TeamOrder
|
||||
reporterId String
|
||||
createdAt DateTime @default(now())
|
||||
match TournamentMatch @relation(fields: [matchId], references: [id])
|
||||
players User[]
|
||||
id String @id @default(uuid())
|
||||
matchId String
|
||||
roundStageId String
|
||||
winner TeamOrder
|
||||
reporterId String
|
||||
createdAt DateTime @default(now())
|
||||
match TournamentMatch @relation(fields: [matchId], references: [id])
|
||||
players User[]
|
||||
roundStage TournamentRoundStage @relation(fields: [roundStageId], references: [id])
|
||||
|
||||
@@unique([matchId, position])
|
||||
@@unique([matchId, roundStageId])
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ export async function seed(variation?: "check-in" | "match") {
|
||||
|
||||
async function advanceRound() {
|
||||
const matches = await prisma.tournamentMatch.findMany({
|
||||
include: { participants: true },
|
||||
include: { participants: true, round: { include: { stages: true } } },
|
||||
});
|
||||
const matchToAdvance = matches.find((match) => match.position === 1);
|
||||
invariant(matchToAdvance);
|
||||
@@ -385,19 +385,25 @@ export async function seed(variation?: "check-in" | "match") {
|
||||
{
|
||||
matchId: matchToAdvance.id,
|
||||
winner: "LOWER",
|
||||
position: 1,
|
||||
roundStageId: matchToAdvance.round.stages.find(
|
||||
(stage) => stage.position === 1
|
||||
)!.id,
|
||||
reporterId: "",
|
||||
},
|
||||
{
|
||||
matchId: matchToAdvance.id,
|
||||
winner: "UPPER",
|
||||
position: 2,
|
||||
roundStageId: matchToAdvance.round.stages.find(
|
||||
(stage) => stage.position === 2
|
||||
)!.id,
|
||||
reporterId: "",
|
||||
},
|
||||
{
|
||||
matchId: matchToAdvance.id,
|
||||
winner: "LOWER",
|
||||
position: 3,
|
||||
roundStageId: matchToAdvance.round.stages.find(
|
||||
(stage) => stage.position === 3
|
||||
)!.id,
|
||||
reporterId: "",
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user