mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-07 19:55:46 -05:00
Add tests and docs to the Swiss module
This commit is contained in:
103
app/features/tournament-bracket/core/Swiss.test.ts
Normal file
103
app/features/tournament-bracket/core/Swiss.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import * as Swiss from "./Swiss";
|
||||
|
||||
describe("Swiss", () => {
|
||||
const createArgsWithDefaults = (
|
||||
args: Partial<Parameters<typeof Swiss.create>[0]> = {},
|
||||
): Parameters<typeof Swiss.create>[0] => {
|
||||
return {
|
||||
name: "Swiss Tournament",
|
||||
seeding: [1, 2, 3, 4],
|
||||
settings: {},
|
||||
tournamentId: 1,
|
||||
...args,
|
||||
};
|
||||
};
|
||||
|
||||
describe("create()", () => {
|
||||
it("attaches the correct tournament id to the data", () => {
|
||||
const data = Swiss.create(createArgsWithDefaults());
|
||||
|
||||
expect(data.stage[0].tournament_id).toBe(1);
|
||||
});
|
||||
|
||||
it("creates a swiss bracket with correct amount of initial matches", () => {
|
||||
const data = Swiss.create(createArgsWithDefaults());
|
||||
|
||||
expect(data.match).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("creates a swiss bracket with correct amount of rounds as default", () => {
|
||||
const data = Swiss.create(createArgsWithDefaults());
|
||||
|
||||
expect(data.round).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("creates a swiss bracket with correct amount of rounds as parameter", () => {
|
||||
const data = Swiss.create(
|
||||
createArgsWithDefaults({
|
||||
settings: {
|
||||
swiss: {
|
||||
groupCount: 1,
|
||||
roundCount: 4,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(data.round).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("creates a swiss bracket with two groups", () => {
|
||||
const data = Swiss.create(
|
||||
createArgsWithDefaults({
|
||||
settings: {
|
||||
swiss: {
|
||||
groupCount: 2,
|
||||
roundCount: 5,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(data.round).toHaveLength(10);
|
||||
|
||||
const matchGroupIds = data.match.map((m) => m.group_id);
|
||||
expect(matchGroupIds).toContain(0);
|
||||
expect(matchGroupIds).toContain(1);
|
||||
});
|
||||
|
||||
it("every team has a match", () => {
|
||||
const data = Swiss.create(createArgsWithDefaults());
|
||||
|
||||
for (const teamId of [1, 2, 3, 4]) {
|
||||
expect(
|
||||
data.match.some(
|
||||
(match) =>
|
||||
match.opponent1?.id === teamId || match.opponent2?.id === teamId,
|
||||
),
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("assigns a BYE if odd number of teams", () => {
|
||||
const data = Swiss.create(
|
||||
createArgsWithDefaults({
|
||||
seeding: [1, 2, 3, 4, 5],
|
||||
}),
|
||||
);
|
||||
|
||||
const byes = data.match.filter((match) => match.opponent2 === null);
|
||||
expect(byes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("throws if not enough teams are provided", () => {
|
||||
expect(() => {
|
||||
Swiss.create(createArgsWithDefaults({ seeding: [1] }));
|
||||
}).toThrow("Not enough teams for Swiss tournament");
|
||||
});
|
||||
});
|
||||
|
||||
// TODO:
|
||||
// describe("generateMatchUps()", () => {});
|
||||
});
|
||||
@@ -8,9 +8,16 @@ import { nullFilledArray } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import type { Bracket, Standing } from "./Bracket";
|
||||
|
||||
/**
|
||||
* Creates a Swiss tournament data set (initial matches) based on the provided arguments. Mimics bracket-manager module's interfaces.
|
||||
*/
|
||||
export function create(
|
||||
args: Omit<InputStage, "type" | "number">,
|
||||
args: Omit<InputStage, "type" | "number" | "seeding"> & { seeding: number[] },
|
||||
): TournamentManagerDataSet {
|
||||
if (args.seeding.length < 2) {
|
||||
throw new Error("Not enough teams for Swiss tournament");
|
||||
}
|
||||
|
||||
const swissSettings = args.settings?.swiss;
|
||||
|
||||
const groupCount =
|
||||
@@ -142,6 +149,13 @@ function firstRoundMatches({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next round of matchups for a Swiss tournament bracket within a specific group.
|
||||
*
|
||||
* Considers only the matches and teams within the specified group. Teams that have dropped out are excluded from the pairing process.
|
||||
* If the group has an uneven number of teams, the lowest standing team that has not already received a bye will receive one.
|
||||
* Matches are generated such that teams do not replay previous opponents if possible.
|
||||
*/
|
||||
export function generateMatchUps({
|
||||
bracket,
|
||||
groupId,
|
||||
|
||||
@@ -86,6 +86,13 @@ export function matchesPlayed({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the standings for a given tournament by aggregating results from relevant brackets.
|
||||
*
|
||||
* For example if the tournament format is round robin (where 2 out of 4 teams per group advance) to single elimination,
|
||||
* the top teams are decided by the single elimination bracket, and the teams who failed to make the bracket are ordered
|
||||
* by their performance in the round robin group stage.
|
||||
*/
|
||||
export function tournamentStandings(tournament: Tournament): Standing[] {
|
||||
const bracketIdxs = Progression.bracketIdxsForStandings(
|
||||
tournament.ctx.settings.bracketProgression,
|
||||
|
||||
Reference in New Issue
Block a user