131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
|
|
import { eq, asc } from "drizzle-orm";
|
||
|
|
import { database } from "~/database/context";
|
||
|
|
import * as schema from "~/database/schema";
|
||
|
|
|
||
|
|
export type PlayoffMatchGame = typeof schema.playoffMatchGames.$inferSelect;
|
||
|
|
export type NewPlayoffMatchGame = typeof schema.playoffMatchGames.$inferInsert;
|
||
|
|
export type PlayoffMatchGameStatus = (typeof schema.playoffMatchGameStatusEnum.enumValues)[number];
|
||
|
|
|
||
|
|
// ─── Pure helpers ────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
export interface SeriesScore {
|
||
|
|
participant1Wins: number;
|
||
|
|
participant2Wins: number;
|
||
|
|
gamesPlayed: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Tally wins from a list of completed games.
|
||
|
|
* participant1Id / participant2Id on the *match* are the two series participants;
|
||
|
|
* winnerId on each game must equal one of them.
|
||
|
|
*/
|
||
|
|
export function computeSeriesScore(
|
||
|
|
games: Pick<PlayoffMatchGame, "winnerId" | "status">[],
|
||
|
|
participant1Id: string,
|
||
|
|
participant2Id: string
|
||
|
|
): SeriesScore {
|
||
|
|
let participant1Wins = 0;
|
||
|
|
let participant2Wins = 0;
|
||
|
|
let gamesPlayed = 0;
|
||
|
|
|
||
|
|
for (const game of games) {
|
||
|
|
if (game.status !== "complete") continue;
|
||
|
|
gamesPlayed++;
|
||
|
|
if (game.winnerId === participant1Id) participant1Wins++;
|
||
|
|
else if (game.winnerId === participant2Id) participant2Wins++;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { participant1Wins, participant2Wins, gamesPlayed };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns true when one participant has enough wins to clinch a best-of-N series.
|
||
|
|
* e.g. best-of-7 requires 4 wins.
|
||
|
|
*/
|
||
|
|
export function isSeriesComplete(
|
||
|
|
games: Pick<PlayoffMatchGame, "winnerId" | "status">[],
|
||
|
|
participant1Id: string,
|
||
|
|
participant2Id: string,
|
||
|
|
requiredWins: number
|
||
|
|
): boolean {
|
||
|
|
const { participant1Wins, participant2Wins } = computeSeriesScore(
|
||
|
|
games,
|
||
|
|
participant1Id,
|
||
|
|
participant2Id
|
||
|
|
);
|
||
|
|
return participant1Wins >= requiredWins || participant2Wins >= requiredWins;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Derive the series leader's participantId, or null if tied / no completed games.
|
||
|
|
*/
|
||
|
|
export function getSeriesLeader(
|
||
|
|
games: Pick<PlayoffMatchGame, "winnerId" | "status">[],
|
||
|
|
participant1Id: string,
|
||
|
|
participant2Id: string
|
||
|
|
): string | null {
|
||
|
|
const { participant1Wins, participant2Wins } = computeSeriesScore(
|
||
|
|
games,
|
||
|
|
participant1Id,
|
||
|
|
participant2Id
|
||
|
|
);
|
||
|
|
if (participant1Wins > participant2Wins) return participant1Id;
|
||
|
|
if (participant2Wins > participant1Wins) return participant2Id;
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── DB operations ───────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
export async function createGame(data: NewPlayoffMatchGame): Promise<PlayoffMatchGame> {
|
||
|
|
const db = database();
|
||
|
|
const [game] = await db
|
||
|
|
.insert(schema.playoffMatchGames)
|
||
|
|
.values(data)
|
||
|
|
.returning();
|
||
|
|
return game;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function findGamesByMatchId(
|
||
|
|
playoffMatchId: string
|
||
|
|
): Promise<PlayoffMatchGame[]> {
|
||
|
|
const db = database();
|
||
|
|
return await db.query.playoffMatchGames.findMany({
|
||
|
|
where: eq(schema.playoffMatchGames.playoffMatchId, playoffMatchId),
|
||
|
|
orderBy: [asc(schema.playoffMatchGames.gameNumber)],
|
||
|
|
with: { winner: true },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function findGameById(id: string): Promise<PlayoffMatchGame | undefined> {
|
||
|
|
const db = database();
|
||
|
|
return await db.query.playoffMatchGames.findFirst({
|
||
|
|
where: eq(schema.playoffMatchGames.id, id),
|
||
|
|
with: { winner: true },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateGame(
|
||
|
|
id: string,
|
||
|
|
data: Partial<NewPlayoffMatchGame>
|
||
|
|
): Promise<PlayoffMatchGame | undefined> {
|
||
|
|
const db = database();
|
||
|
|
const [game] = await db
|
||
|
|
.update(schema.playoffMatchGames)
|
||
|
|
.set({ ...data, updatedAt: new Date() })
|
||
|
|
.where(eq(schema.playoffMatchGames.id, id))
|
||
|
|
.returning();
|
||
|
|
return game;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteGame(id: string): Promise<void> {
|
||
|
|
const db = database();
|
||
|
|
await db.delete(schema.playoffMatchGames).where(eq(schema.playoffMatchGames.id, id));
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteGamesByMatchId(playoffMatchId: string): Promise<void> {
|
||
|
|
const db = database();
|
||
|
|
await db
|
||
|
|
.delete(schema.playoffMatchGames)
|
||
|
|
.where(eq(schema.playoffMatchGames.playoffMatchId, playoffMatchId));
|
||
|
|
}
|