sendou.ink/app/features/tournament-bracket/tournament-bracket-utils.ts
Kalle ef78d3a2c2
Tournament full (#1373)
* Got something going

* Style overwrites

* width != height

* More playing with lines

* Migrations

* Start bracket initial

* Unhardcode stage generation params

* Link to match page

* Matches page initial

* Support directly adding seed to map list generator

* Add docs

* Maps in matches page

* Add invariant about tie breaker map pool

* Fix PICNIC lacking tie breaker maps

* Only link in bracket when tournament has started

* Styled tournament roster inputs

* Prefer IGN in tournament match page

* ModeProgressIndicator

* Some conditional rendering

* Match action initial + better error display

* Persist bestOf in DB

* Resolve best of ahead of time

* Move brackets-manager to core

* Score reporting works

* Clear winner on score report

* ModeProgressIndicator: highlight winners

* Fix inconsistent input

* Better text when submitting match

* mapCountPlayedInSetWithCertainty that works

* UNDO_REPORT_SCORE implemented

* Permission check when starting tournament

* Remove IGN from upsert

* View match results page

* Source in DB

* Match page waiting for teams

* Move tournament bracket to feature folder

* REOPEN_MATCH initial

* Handle proper resetting of match

* Inline bracket-manager

* Syncify

* Transactions

* Handle match is locked gracefully

* Match page auto refresh

* Fix match refresh called "globally"

* Bracket autoupdate

* Move fillWithNullTillPowerOfTwo to utils with testing

* Fix map lists not visible after tournament started

* Optimize match events

* Show UI while in progress to members

* Fix start tournament alert not being responsive

* Teams can check in

* Fix map list 400

* xxx -> TODO

* Seeds page

* Remove map icons for team page

* Don't display link to seeds after tournament has started

* Admin actions initial

* Change captain admin action

* Make all hooks ts

* Admin actions functioning

* Fix validate error not displaying in CatchBoundary

* Adjust validate args order

* Remove admin loader

* Make delete team button menancing

* Only include checked in teams to bracket

* Optimize to.id route loads

* Working show map list generator toggle

* Update full tournaments flow

* Make full tournaments work with many start times

* Handle undefined in crud

* Dynamic stage banner

* Handle default strat if map list generation fails

* Fix crash on brackets if less than 2 teams

* Add commented out test for reference

* Add TODO

* Add players from team during register

* TrustRelationship

* Prefers not to host feature

* Last before merge

* Rename some vars

* More renames
2023-05-15 22:37:43 +03:00

144 lines
3.5 KiB
TypeScript

import type { Stage } from "brackets-model";
import type {
TournamentFormat,
TournamentMatch,
TournamentStage,
} from "~/db/types";
import {
sourceTypes,
seededRandom,
} from "~/modules/tournament-map-list-generator";
import { assertUnreachable } from "~/utils/types";
import type { FindMatchById } from "../tournament-bracket/queries/findMatchById.server";
import type {
TournamentLoaderData,
TournamentLoaderTeam,
} from "~/features/tournament";
import type { Params } from "@remix-run/react";
import invariant from "tiny-invariant";
export function matchIdFromParams(params: Params<string>) {
const result = Number(params["mid"]);
invariant(!Number.isNaN(result), "mid is not a number");
return result;
}
const passNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
export function resolveRoomPass(matchId: TournamentMatch["id"]) {
let result = "";
for (let i = 0; i < 4; i++) {
const { shuffle } = seededRandom(`${matchId}-${i}`);
result += shuffle(passNumbers)[0];
}
return result;
}
export function resolveHostingTeam(
teams: [TournamentLoaderTeam, TournamentLoaderTeam]
) {
if (teams[0].prefersNotToHost && !teams[1].prefersNotToHost) return teams[1];
if (!teams[0].prefersNotToHost && teams[1].prefersNotToHost) return teams[0];
if (!teams[0].seed && !teams[1].seed) return teams[0];
if (!teams[0].seed) return teams[1];
if (!teams[1].seed) return teams[0];
if (teams[0].seed < teams[1].seed) return teams[0];
if (teams[1].seed < teams[0].seed) return teams[1];
console.error("resolveHostingTeam: unexpected default");
return teams[0];
}
export function resolveTournamentStageName(format: TournamentFormat) {
switch (format) {
case "SE":
case "DE":
return "Elimination stage";
default: {
assertUnreachable(format);
}
}
}
export function resolveTournamentStageType(
format: TournamentFormat
): TournamentStage["type"] {
switch (format) {
case "SE":
return "single_elimination";
case "DE":
return "double_elimination";
default: {
assertUnreachable(format);
}
}
}
export function resolveTournamentStageSettings(
format: TournamentFormat
): Stage["settings"] {
switch (format) {
case "SE":
return {};
case "DE":
return { grandFinal: "double" };
default: {
assertUnreachable(format);
}
}
}
export function mapCountPlayedInSetWithCertainty({
bestOf,
scores,
}: {
bestOf: number;
scores: [number, number];
}) {
const maxScore = Math.max(...scores);
const scoreSum = scores.reduce((acc, curr) => acc + curr, 0);
return scoreSum + (Math.ceil(bestOf / 2) - maxScore);
}
export function checkSourceIsValid({
source,
match,
}: {
source: string;
match: NonNullable<FindMatchById>;
}) {
if (sourceTypes.includes(source as any)) return true;
const asTeamId = Number(source);
if (match.opponentOne?.id === asTeamId) return true;
if (match.opponentTwo?.id === asTeamId) return true;
return false;
}
export function HACKY_resolvePoolCode(event: TournamentLoaderData["event"]) {
if (event.name.includes("In The Zone")) return "ITZ";
return "PICNIC";
}
export function bracketSubscriptionKey(tournamentId: number) {
return `BRACKET_CHANGED_${tournamentId}`;
}
export function matchSubscriptionKey(matchId: number) {
return `MATCH_CHANGED_${matchId}`;
}
export function fillWithNullTillPowerOfTwo<T>(arr: T[]) {
const nextPowerOfTwo = Math.pow(2, Math.ceil(Math.log2(arr.length)));
const nullsToAdd = nextPowerOfTwo - arr.length;
return [...arr, ...new Array(nullsToAdd).fill(null)];
}