mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-23 11:11:52 -05:00
Remove size
This commit is contained in:
parent
6d96069d56
commit
5facba4e42
|
|
@ -117,7 +117,7 @@ function createSingleEliminationData(): TournamentManagerDataSet {
|
|||
number: 1,
|
||||
type: "single_elimination",
|
||||
tournament_id: 1,
|
||||
settings: { size: 8 },
|
||||
settings: {},
|
||||
},
|
||||
],
|
||||
group: [{ id: 1, number: 1, stage_id: 1 }],
|
||||
|
|
@ -228,7 +228,7 @@ function createByeHeavySingleEliminationData(): TournamentManagerDataSet {
|
|||
number: 1,
|
||||
type: "single_elimination",
|
||||
tournament_id: 1,
|
||||
settings: { size: 8 },
|
||||
settings: {},
|
||||
},
|
||||
],
|
||||
group: [{ id: 1, number: 1, stage_id: 1 }],
|
||||
|
|
@ -342,7 +342,7 @@ function createDoubleEliminationData(): TournamentManagerDataSet {
|
|||
number: 1,
|
||||
type: "double_elimination",
|
||||
tournament_id: 1,
|
||||
settings: { size: 4 },
|
||||
settings: {},
|
||||
},
|
||||
],
|
||||
group: [
|
||||
|
|
@ -443,7 +443,7 @@ function createRoundRobinData(): TournamentManagerDataSet {
|
|||
number: 1,
|
||||
type: "round_robin",
|
||||
tournament_id: 1,
|
||||
settings: { groupCount: 2, size: 6 },
|
||||
settings: { groupCount: 2 },
|
||||
},
|
||||
],
|
||||
group: [
|
||||
|
|
@ -673,7 +673,7 @@ function createLargeSingleEliminationData(options?: {
|
|||
number: 1,
|
||||
type: "single_elimination",
|
||||
tournament_id: 1,
|
||||
settings: { size: 16 },
|
||||
settings: {},
|
||||
},
|
||||
],
|
||||
group: [{ id: 1, number: 1, stage_id: 1 }],
|
||||
|
|
|
|||
|
|
@ -29,24 +29,22 @@ import {
|
|||
export class StageCreator {
|
||||
readonly input: ResolvedCreateBracketInput;
|
||||
settings: StageSettings;
|
||||
seeding: Seeding | undefined;
|
||||
seeding: Seeding;
|
||||
readonly data: BracketData;
|
||||
|
||||
constructor(input: ResolvedCreateBracketInput) {
|
||||
this.input = input;
|
||||
this.settings = structuredClone(input.settings) ?? {};
|
||||
const seeding = input.seeding ? [...input.seeding] : undefined;
|
||||
this.seeding =
|
||||
seeding && input.type !== "round_robin"
|
||||
? padSeedingToPowerOfTwo(seeding)
|
||||
: seeding;
|
||||
this.data = { stage: [], group: [], round: [], match: [] };
|
||||
|
||||
if (!input.name) throw Error("You must provide a name for the stage.");
|
||||
|
||||
if (!Number.isInteger(input.tournamentId))
|
||||
throw Error("You must provide a tournament id for the stage.");
|
||||
|
||||
this.input = input;
|
||||
this.settings = structuredClone(input.settings) ?? {};
|
||||
const seeding = [...input.seeding];
|
||||
this.seeding =
|
||||
input.type !== "round_robin" ? padSeedingToPowerOfTwo(seeding) : seeding;
|
||||
this.data = { stage: [], group: [], round: [], match: [] };
|
||||
|
||||
if (input.type === "single_elimination")
|
||||
this.settings.consolationFinal = this.settings.consolationFinal || false;
|
||||
}
|
||||
|
|
@ -161,7 +159,7 @@ export class StageCreator {
|
|||
number: number,
|
||||
losers: ParticipantSlot[][],
|
||||
): ParticipantSlot {
|
||||
const participantCount = this.settings.size!;
|
||||
const participantCount = this.seeding.length;
|
||||
const roundPairCount = helpers.getRoundPairCount(participantCount);
|
||||
|
||||
let losersId = 0;
|
||||
|
|
@ -330,31 +328,13 @@ export class StageCreator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a list of slots.
|
||||
* - If `seeding` was given, uses it.
|
||||
* - If `size` was given, only returns a list of empty slots.
|
||||
* Returns the list of slots from the seeding.
|
||||
*
|
||||
* @param positions An optional list of positions (seeds) for a manual ordering.
|
||||
*/
|
||||
getSlots(positions?: number[]): ParticipantSlot[] {
|
||||
const size = this.settings.size || this.seeding?.length || 0;
|
||||
helpers.ensureValidSize(this.input.type, size);
|
||||
|
||||
if (size && !this.seeding)
|
||||
return Array.from(Array(size), (_: ParticipantSlot, i) => ({
|
||||
id: null,
|
||||
position: i + 1,
|
||||
}));
|
||||
|
||||
if (!this.seeding) throw Error("Either size or seeding must be given.");
|
||||
|
||||
this.settings = {
|
||||
...this.settings,
|
||||
size, // Always set the size.
|
||||
};
|
||||
|
||||
helpers.ensureValidSize(this.input.type, this.seeding.length);
|
||||
helpers.ensureNoDuplicates(this.seeding);
|
||||
this.seeding = helpers.fixSeeding(this.seeding, size);
|
||||
|
||||
return this.getSlotsUsingIds(this.seeding, positions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe("Create double elimination stage", () => {
|
|||
name: "Example with 256 participants",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
settings: { size: 256 },
|
||||
seeding: Array.from({ length: 256 }, (_, i) => i + 1),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function createDoubleElimination(creator: StageCreator): void {
|
|||
ordered,
|
||||
);
|
||||
|
||||
if (helpers.isDoubleEliminationNecessary(creator.settings.size!)) {
|
||||
if (helpers.isDoubleEliminationNecessary(slots.length)) {
|
||||
const winnerLb = creator.createLowerBracket(stage.id, 2, losersWb);
|
||||
createGrandFinal(creator, stage.id, winnerWb, winnerLb);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function create(input: CreateBracketInput): CreatedBracket {
|
|||
/**
|
||||
* Engine-internal `create` taking already-resolved internal stage settings.
|
||||
* Tests use this to control knobs that are an implementation detail to the
|
||||
* app (seed ordering, byes balancing, TBD slots via `settings.size`).
|
||||
* app (seed ordering, byes balancing).
|
||||
*/
|
||||
export function createResolved(
|
||||
input: ResolvedCreateBracketInput,
|
||||
|
|
|
|||
|
|
@ -144,14 +144,14 @@ describe("Create a round-robin stage", () => {
|
|||
expect(realMatchRound.number).toBe(1);
|
||||
});
|
||||
|
||||
test("should create a round-robin stage with to be determined participants", () => {
|
||||
test("should create a round-robin stage split across multiple groups", () => {
|
||||
bracket.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: Array.from({ length: 16 }, (_, i) => i + 1),
|
||||
settings: {
|
||||
groupCount: 4,
|
||||
size: 16,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -181,6 +181,7 @@ describe("Create a round-robin stage", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [1, 2, 3, 4],
|
||||
}),
|
||||
).toThrow("You must specify a group count for round-robin stages.");
|
||||
});
|
||||
|
|
@ -191,9 +192,9 @@ describe("Create a round-robin stage", () => {
|
|||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "round_robin",
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {
|
||||
groupCount: 0,
|
||||
size: 4,
|
||||
},
|
||||
}),
|
||||
).toThrow("You must provide a strictly positive group count.");
|
||||
|
|
|
|||
|
|
@ -37,10 +37,8 @@ describe("BYE handling", () => {
|
|||
name: "Example with BYEs",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
seeding: [1, 2],
|
||||
settings: {
|
||||
size: 4,
|
||||
},
|
||||
seeding: [1, 2, null, null],
|
||||
settings: {},
|
||||
});
|
||||
|
||||
expect(bracket.match(0).opponent1?.id).toBe(1);
|
||||
|
|
@ -59,9 +57,8 @@ describe("Position checks", () => {
|
|||
name: "Example with double grand final",
|
||||
tournamentId: 0,
|
||||
type: "double_elimination",
|
||||
settings: {
|
||||
size: 8,
|
||||
},
|
||||
seeding: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
settings: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -132,37 +129,22 @@ describe("Special cases", () => {
|
|||
expect(bracket.match(0).opponent2).toBe(null);
|
||||
});
|
||||
|
||||
test("should throw if the size of a stage is not a power of two", () => {
|
||||
expect(() =>
|
||||
bracket.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
settings: { size: 3 },
|
||||
}),
|
||||
).toThrow(
|
||||
"The library only supports a participant count which is a power of two.",
|
||||
);
|
||||
});
|
||||
|
||||
test("should throw if the participant count of a stage is less than two", () => {
|
||||
expect(() =>
|
||||
bracket.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
settings: { size: 0 },
|
||||
seeding: [],
|
||||
}),
|
||||
).toThrow(
|
||||
"Impossible to create an empty stage. If you want an empty seeding, just set the size of the stage.",
|
||||
);
|
||||
).toThrow("Impossible to create a stage with less than 2 participants.");
|
||||
|
||||
expect(() =>
|
||||
bracket.create({
|
||||
name: "Example",
|
||||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
settings: { size: 1 },
|
||||
seeding: [1],
|
||||
}),
|
||||
).toThrow("Impossible to create a stage with less than 2 participants.");
|
||||
});
|
||||
|
|
@ -218,9 +200,7 @@ describe("Reset match", () => {
|
|||
tournamentId: 0,
|
||||
type: "single_elimination",
|
||||
seeding: [1, null, null, null, null, null, null, 2],
|
||||
settings: {
|
||||
size: 8,
|
||||
},
|
||||
settings: {},
|
||||
});
|
||||
|
||||
bracket.updateMatch({
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type {
|
|||
ParticipantResult,
|
||||
ParticipantSlot,
|
||||
Result,
|
||||
Seeding,
|
||||
SeedOrdering,
|
||||
Side,
|
||||
StageData,
|
||||
|
|
@ -294,27 +293,6 @@ export function ensureNoDuplicates<T>(array: (T | null)[]): void {
|
|||
throw new Error("The seeding has a duplicate participant.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the seeding by enlarging it if it's not complete.
|
||||
*
|
||||
* @param seeding The seeding of the stage.
|
||||
* @param participantCount The number of participants in the stage.
|
||||
*/
|
||||
export function fixSeeding(
|
||||
seeding: Seeding,
|
||||
participantCount: number,
|
||||
): Seeding {
|
||||
if (seeding.length > participantCount)
|
||||
throw Error(
|
||||
"The seeding has more participants than the size of the stage.",
|
||||
);
|
||||
|
||||
if (seeding.length < participantCount)
|
||||
return Array.from(Array(participantCount), (_, i) => seeding[i] || null);
|
||||
|
||||
return seeding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the participant count is valid.
|
||||
*
|
||||
|
|
@ -325,11 +303,6 @@ export function ensureValidSize(
|
|||
stageType: StageType,
|
||||
participantCount: number,
|
||||
): void {
|
||||
if (participantCount === 0)
|
||||
throw Error(
|
||||
"Impossible to create an empty stage. If you want an empty seeding, just set the size of the stage.",
|
||||
);
|
||||
|
||||
if (participantCount < 2)
|
||||
throw Error("Impossible to create a stage with less than 2 participants.");
|
||||
|
||||
|
|
|
|||
|
|
@ -417,7 +417,7 @@ export class Propagator {
|
|||
roundNumber: number,
|
||||
): MatchData[] {
|
||||
if (stage.type === "single_elimination")
|
||||
return this.getPreviousMatchesFinalSingleElimination(match, stage);
|
||||
return this.getPreviousMatchesFinalSingleElimination(match);
|
||||
|
||||
return this.getPreviousMatchesFinalDoubleElimination(match, roundNumber);
|
||||
}
|
||||
|
|
@ -426,15 +426,13 @@ export class Propagator {
|
|||
* Gets the matches leading to the given match, which is in a final group (consolation final).
|
||||
*
|
||||
* @param match The current match.
|
||||
* @param stage The parent stage.
|
||||
*/
|
||||
private getPreviousMatchesFinalSingleElimination(
|
||||
match: MatchData,
|
||||
stage: StageData,
|
||||
): MatchData[] {
|
||||
const upperBracket = this.getUpperBracket(match.stage_id);
|
||||
const upperBracketRoundCount = helpers.getUpperBracketRoundCount(
|
||||
stage.settings.size!,
|
||||
this.participantCount(match.stage_id),
|
||||
);
|
||||
|
||||
const semiFinalsRound = this.store.selectFirst("round", {
|
||||
|
|
@ -648,7 +646,7 @@ export class Propagator {
|
|||
|
||||
const roundNumberLB = roundNumber > 1 ? (roundNumber - 1) * 2 : 1;
|
||||
|
||||
const participantCount = stage.settings.size!;
|
||||
const participantCount = this.participantCount(match.stage_id);
|
||||
const method = helpers.getLoserOrdering(participantCount, roundNumberLB);
|
||||
const actualMatchNumberLB = helpers.findLoserMatchNumber(
|
||||
participantCount,
|
||||
|
|
@ -860,6 +858,26 @@ export class Propagator {
|
|||
return winnerBracket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the participant count of an elimination stage, derived from its upper
|
||||
* bracket's first round (two participants per match, BYEs included).
|
||||
*
|
||||
* @param stageId ID of the stage.
|
||||
*/
|
||||
private participantCount(stageId: number): number {
|
||||
const upperBracket = this.getUpperBracket(stageId);
|
||||
const firstRound = this.store.selectFirst("round", {
|
||||
group_id: upperBracket.id,
|
||||
number: 1,
|
||||
});
|
||||
if (!firstRound) throw Error("First round not found.");
|
||||
|
||||
const firstRoundMatches = this.store.selectAll("match", {
|
||||
round_id: firstRound.id,
|
||||
});
|
||||
return firstRoundMatches.length * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the loser bracket.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ export type MatchStatus = (typeof MatchStatus)[keyof typeof MatchStatus];
|
|||
* TournamentStage.settings today (the old brackets-model StageSettings).
|
||||
*/
|
||||
export interface StageSettings {
|
||||
// xxx: why not inferred?
|
||||
/** The number of participants. */
|
||||
size?: number;
|
||||
|
||||
/** Number of groups in a round-robin stage. */
|
||||
groupCount?: number;
|
||||
|
||||
|
|
@ -230,15 +226,10 @@ export interface CreateBracketInput {
|
|||
|
||||
/**
|
||||
* Engine-internal variant of {@link CreateBracketInput}: settings are the
|
||||
* already-resolved internal {@link StageSettings} and seeding may be omitted
|
||||
* in favor of `settings.size` (TBD slots).
|
||||
* already-resolved internal {@link StageSettings}.
|
||||
*/
|
||||
export interface ResolvedCreateBracketInput
|
||||
extends Omit<
|
||||
CreateBracketInput,
|
||||
"seeding" | "settings" | "independentRounds"
|
||||
> {
|
||||
seeding?: Seeding;
|
||||
extends Omit<CreateBracketInput, "settings" | "independentRounds"> {
|
||||
settings: StageSettings;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({
|
|||
id: 1429,
|
||||
name: "Beta Bracket",
|
||||
number: 2,
|
||||
settings: {
|
||||
size: 16,
|
||||
},
|
||||
settings: {},
|
||||
tournament_id: 815,
|
||||
type: "double_elimination",
|
||||
createdAt: 1734286247,
|
||||
|
|
@ -33,9 +31,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({
|
|||
id: 1430,
|
||||
name: "Alpha Bracket",
|
||||
number: 3,
|
||||
settings: {
|
||||
size: 16,
|
||||
},
|
||||
settings: {},
|
||||
tournament_id: 815,
|
||||
type: "double_elimination",
|
||||
createdAt: 1734286249,
|
||||
|
|
@ -44,9 +40,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({
|
|||
id: 1431,
|
||||
name: "Gamma Bracket",
|
||||
number: 4,
|
||||
settings: {
|
||||
size: 16,
|
||||
},
|
||||
settings: {},
|
||||
tournament_id: 815,
|
||||
type: "double_elimination",
|
||||
createdAt: 1734286340,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export const SWIM_OR_SINK_167 = (
|
|||
number: 1,
|
||||
settings: {
|
||||
groupCount: 11,
|
||||
size: 44,
|
||||
},
|
||||
tournament_id: 672,
|
||||
type: "round_robin",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export const PADDLING_POOL_257 = () =>
|
|||
number: 1,
|
||||
settings: {
|
||||
groupCount: 9,
|
||||
size: 35,
|
||||
},
|
||||
tournament_id: 27,
|
||||
type: "round_robin",
|
||||
|
|
@ -6822,7 +6821,6 @@ export const PADDLING_POOL_255 = () =>
|
|||
number: 1,
|
||||
settings: {
|
||||
groupCount: 9,
|
||||
size: 35,
|
||||
},
|
||||
tournament_id: 18,
|
||||
type: "round_robin",
|
||||
|
|
@ -14210,9 +14208,7 @@ export const IN_THE_ZONE_32 = ({
|
|||
id: 16,
|
||||
name: "Main bracket",
|
||||
number: 1,
|
||||
settings: {
|
||||
size: 32,
|
||||
},
|
||||
settings: {},
|
||||
tournament_id: 11,
|
||||
type: "double_elimination",
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user