Fix Swiss algorithm not always finding the correct team to give bye to

This commit is contained in:
Kalle 2026-08-04 21:52:31 +03:00
parent d0654a3201
commit 5adf4be0eb
2 changed files with 33 additions and 5 deletions

View File

@ -358,6 +358,24 @@ describe("Swiss", () => {
expect(includesPair(result, 1, 2)).toBe(false);
});
it("prefers giving the bye to the lowest standing team without a previous bye", () => {
// five team swiss entering round 4: teams 3, 4 and 5 have already had a
// bye, team 3 in the round right before this one. Teams 1 and 2 have not,
// and a rematch free pairing where team 2 (the lowest standing team
// without a previous bye) gets the bye exists: 1-4, 3-5
const result = Swiss.pairUp([
{ id: 1, score: 3, avoid: [3, 5, 2] },
{ id: 2, score: 2, avoid: [4, 3, 1] },
{ id: 4, score: 2, avoid: [2, 5], receivedBye: true },
{ id: 3, score: 1, avoid: [1, 2], receivedBye: true },
{ id: 5, score: 1, avoid: [1, 4], receivedBye: true },
]);
const bye = result.find((match) => match.opponentTwo === null);
expect(bye?.opponentOne).toBe(2);
});
});
describe("calculateTeamStatus()", () => {

View File

@ -18,6 +18,16 @@ import { calculateTeamStatus } from "./team-status";
*/
const REMATCH_PENALTY = 1_000_000;
/**
* Weight added for each team of a pair that has already received a bye, making
* previously-byed teams more attractive to pair up. Applied per team (not per pair)
* so that a matching leaving a bye-less team unpaired always beats one giving a team
* a second bye: with an odd team count exactly one team is left unpaired, so the
* matchings differ by exactly one bonus, which is set to dwarf every score-based
* weight while staying far below REMATCH_PENALTY.
*/
const PRIOR_BYE_PAIRING_BONUS = 10_000;
interface GroupArgs {
groupId: number;
standings: SwissStanding[];
@ -285,11 +295,11 @@ function generateWeightedPairs({
wt -= 10;
}
if (
(Object.hasOwn(curr, "receivedBye") && curr.receivedBye) ||
(Object.hasOwn(opp, "receivedBye") && opp.receivedBye)
) {
wt += 40;
if (curr.receivedBye) {
wt += PRIOR_BYE_PAIRING_BONUS;
}
if (opp.receivedBye) {
wt += PRIOR_BYE_PAIRING_BONUS;
}
wt -= timesPlayed(curr, opp) * REMATCH_PENALTY;