diff --git a/app/components/tournament/BracketActions.tsx b/app/components/tournament/BracketActions.tsx
index 60fb40b7e..d9e1ac3fd 100644
--- a/app/components/tournament/BracketActions.tsx
+++ b/app/components/tournament/BracketActions.tsx
@@ -90,7 +90,7 @@ export function BracketActions() {
);
invariant(matchWeAreWaitingFor, "matchWeAreWaitingFor is undefined");
- if (matchWeAreWaitingFor.participants?.length !== 2) {
+ if (matchWeAreWaitingFor.participants?.filter(Boolean).length !== 2) {
return (
Waiting on match number {matchWeAreWaitingFor.number} (missing teams)
@@ -104,7 +104,7 @@ export function BracketActions() {
{matchWeAreWaitingFor.participants[1]}
{(matchWeAreWaitingFor.score ?? [0, 0]).join("-")} - Best of{" "}
- {matchWeAreWaitingFor.round.matches.length}
+ {matchWeAreWaitingFor.round.stages.length}
);
diff --git a/app/models/TournamentBracket.ts b/app/models/TournamentBracket.ts
index 9f0d949b0..9350fb523 100644
--- a/app/models/TournamentBracket.ts
+++ b/app/models/TournamentBracket.ts
@@ -26,6 +26,7 @@ export function findById(bracketId: string) {
select: {
team: {
select: {
+ id: true,
name: true,
},
},
diff --git a/app/models/TournamentMatch.ts b/app/models/TournamentMatch.ts
index 07cf5b60d..e59300ef1 100644
--- a/app/models/TournamentMatch.ts
+++ b/app/models/TournamentMatch.ts
@@ -58,3 +58,11 @@ export function createResult({
},
});
}
+
+export function createParticipants(
+ data: ({ matchId: string; order: TeamOrder; teamId: string } | undefined)[]
+) {
+ return db.tournamentMatchParticipant.createMany({
+ data: data.flatMap((result) => result ?? []),
+ });
+}
diff --git a/app/routes/to/$organization.$tournament/bracket.$id.tsx b/app/routes/to/$organization.$tournament/bracket.$id.tsx
index 3fb9ff7bc..82925f4f1 100644
--- a/app/routes/to/$organization.$tournament/bracket.$id.tsx
+++ b/app/routes/to/$organization.$tournament/bracket.$id.tsx
@@ -46,6 +46,7 @@ type ActionData = {
};
export const action: ActionFunction = async ({
+ params,
request,
context,
}): Promise => {
@@ -53,7 +54,7 @@ export const action: ActionFunction = async ({
request,
schema: bracketActionSchema,
});
-
+ invariant(typeof params.id === "string", "Expected params.id to be string");
const user = requireUser(context);
switch (data._action) {
@@ -64,6 +65,7 @@ export const action: ActionFunction = async ({
userId: user.id,
winnerTeamId: data.winnerTeamId,
position: data.position,
+ bracketId: params.id,
});
return { ok: "REPORT_SCORE" };
}
diff --git a/app/services/tournament.ts b/app/services/tournament.ts
index 1ecd6879d..cedc9e938 100644
--- a/app/services/tournament.ts
+++ b/app/services/tournament.ts
@@ -1,4 +1,4 @@
-import type { Prisma, Stage, TournamentMatchGameResult } from ".prisma/client";
+import type { Prisma, Stage, TeamOrder } from ".prisma/client";
import {
TOURNAMENT_CHECK_IN_CLOSING_MINUTES_FROM_START,
TOURNAMENT_TEAM_ROSTER_MAX_SIZE,
@@ -538,12 +538,14 @@ export async function reportScore({
matchId,
playerIds,
position,
+ bracketId,
}: {
userId: string;
winnerTeamId: string;
matchId: string;
playerIds: string[];
position: number;
+ bracketId: string;
}) {
const match = await TournamentMatch.findById(matchId);
if (!match) throw new Response("Invalid match id", { status: 400 });
@@ -574,17 +576,70 @@ export async function reportScore({
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,
- roundStageId: stage.id,
- reporterId: userId,
- winner: winnerTeam.order,
- });
+ // advance tournament after reporting score if match is over
+ if (
+ matchIsOver(
+ match.round.stages.length,
+ matchResultsToTuple(
+ match.results
+ .map((r) => ({ winner: r.winner }))
+ .concat([{ winner: winnerTeam.order }])
+ )
+ )
+ ) {
+ const loserTeam = match.participants.find((p) => p.teamId !== winnerTeamId);
+ invariant(loserTeam, "loserTeamId is undefined");
+
+ const bracket = await TournamentBracket.findById(bracketId);
+ if (!bracket) throw new Response("Invalid bracket id", { status: 400 });
+
+ return db.$transaction([
+ TournamentMatch.createResult({
+ matchId,
+ playerIds,
+ roundStageId: stage.id,
+ reporterId: userId,
+ winner: winnerTeam.order,
+ }),
+ // todo: bracket reset
+ TournamentMatch.createParticipants([
+ match.winnerDestinationMatchId
+ ? {
+ matchId: match.winnerDestinationMatchId,
+ order: resolveNewOrder({
+ bracket,
+ oldMatch: match,
+ newMatchId: match.winnerDestinationMatchId,
+ }),
+ teamId: winnerTeam.teamId,
+ }
+ : undefined,
+ match.loserDestinationMatchId
+ ? {
+ matchId: match.loserDestinationMatchId,
+ order: resolveNewOrder({
+ bracket,
+ oldMatch: match,
+ newMatchId: match.loserDestinationMatchId,
+ }),
+ teamId: loserTeam?.teamId,
+ }
+ : undefined,
+ ]),
+ ]);
+ // otherwise if set is not over simply create result and return
+ } else {
+ return TournamentMatch.createResult({
+ matchId,
+ playerIds,
+ roundStageId: stage.id,
+ reporterId: userId,
+ winner: winnerTeam.order,
+ });
+ }
}
-function matchResultsToTuple(results: TournamentMatchGameResult[]) {
+function matchResultsToTuple(results: { winner: TeamOrder }[]) {
return results.reduce(
(acc: [number, number], result) => {
if (result.winner === "UPPER") acc[0]++;
@@ -594,3 +649,44 @@ function matchResultsToTuple(results: TournamentMatchGameResult[]) {
[0, 0]
);
}
+
+function resolveNewOrder({
+ bracket,
+ oldMatch,
+ newMatchId,
+}: {
+ bracket: NonNullable;
+ oldMatch: NonNullable;
+ newMatchId: string;
+}): TeamOrder {
+ const allMatches = bracket.rounds.flat().flatMap((round) => {
+ return round.matches;
+ });
+
+ const newMatch = allMatches.find((m) => m.id === newMatchId);
+ invariant(newMatch, "newMatch is undefined");
+
+ const matchesThatLeadToNewMatch = allMatches
+ .filter((m) =>
+ [m.loserDestinationMatchId, m.winnerDestinationMatchId].includes(
+ newMatchId
+ )
+ )
+ .sort((a, b) => a.position - b.position);
+ invariant(
+ matchesThatLeadToNewMatch.length === 2,
+ `matchesThatLeadToNewMatch length was unexpected: ${matchesThatLeadToNewMatch.length}`
+ );
+ console.log(JSON.stringify({ matchesThatLeadToNewMatch, oldMatch }, null, 2));
+ invariant(
+ matchesThatLeadToNewMatch.find((m) => m.id === oldMatch.id),
+ "oldMatch not among matchesThatLeadToNewMatch"
+ );
+
+ // if match number is smaller it should mean the match is above
+ // the other match. thanks to sorting above 0 index should have
+ // the smaller match number. Winner's bracket match should
+ // always have the smaller number compared to loser's bracket match
+ if (matchesThatLeadToNewMatch[0].id === oldMatch.id) return "UPPER";
+ return "LOWER";
+}
diff --git a/app/styles/tournament-bracket.css b/app/styles/tournament-bracket.css
index 9a483e328..9a35ee084 100644
--- a/app/styles/tournament-bracket.css
+++ b/app/styles/tournament-bracket.css
@@ -65,7 +65,7 @@
margin: 0 auto;
column-gap: var(--s-6);
grid-template-columns: 1fr 1fr;
- row-gap: var(--s-2-5);
+ row-gap: var(--s-1-5);
}
.tournament-bracket__infos__label {