Tournament is a TypeScript library for orchestrating chess tournaments using
any FIDE pairing system. It provides a stateful Tournament class that manages
the full lifecycle — pairing rounds, recording results, computing standings —
and a bakuAcceleration() function implementing FIDE C.04.7. Zero runtime
dependencies.
The pairing system is injected as a function parameter. Bring your own from
@echecs/swiss,
@echecs/round-robin, or a
custom implementation.
npm install @echecs/tournamentimport { Tournament } from '@echecs/tournament';
import { dutch } from '@echecs/swiss';
import type { Game, GameKind, Player, Tiebreak } from '@echecs/tournament';
const players: Player[] = [
{ id: 'alice', rating: 2100 },
{ id: 'bob', rating: 1950 },
{ id: 'carol', rating: 1870 },
{ id: 'dave', rating: 1820 },
];
const tournament = new Tournament({
pairingSystem: dutch,
players,
rounds: 3,
});
// Round 1
const round1 = tournament.pairRound();
// round1.pairings = [{ black: 'carol', white: 'alice' }, ...]
// round1.byes = [{ player: 'dave' }]
tournament.recordResult({ black: 'carol', result: 1, white: 'alice' });
tournament.recordResult({ black: 'dave', result: 0.5, white: 'bob' });
// Round 2
const round2 = tournament.pairRound();
// ...record results...
// Standings (no tiebreaks)
const table = tournament.standings();
// [{ player: 'alice', rank: 1, score: 2, tiebreaks: [] }, ...]class Tournament {
constructor(options: TournamentOptions);
pairRound(): PairingResult;
recordResult(game: Game): void;
standings(tiebreaks?: Tiebreak[]): Standing[];
get currentRound(): number;
get games(): Game[][];
get isComplete(): boolean;
get players(): Player[];
get rounds(): number;
toJSON(): TournamentSnapshot;
static fromJSON(
snapshot: TournamentSnapshot,
pairingSystem: PairingSystem,
acceleration?: AccelerationMethod,
): Tournament;
}Creates a new tournament.
interface TournamentOptions {
acceleration?: AccelerationMethod; // e.g. bakuAcceleration(players)
pairingSystem: PairingSystem; // e.g. dutch, roundRobin
players: Player[]; // all participants
rounds: number; // total number of rounds
}Throws RangeError if fewer than 2 players or fewer than 1 round.
Generates pairings for the next round using the injected pairing system. Returns
a PairingResult with pairings and byes.
Throws RangeError if the tournament is complete or the current round has
unrecorded results.
Records a game result for the current round.
tournament.recordResult({
black: 'carol',
result: 1, // 1 = white wins, 0.5 = draw, 0 = black wins
white: 'alice',
});The optional kind?: GameKind field classifies the game type:
type GameKind = 'forfeit' | 'normal' | 'rated' | 'unrated';Throws RangeError if the players don't match any pairing in the current round.
Returns players ranked by score, with optional tiebreaks applied in order. Each
tiebreak function receives (playerId, games, players) and returns a number.
import { buchholz } from '@echecs/buchholz';
import { sonnebornBerger } from '@echecs/sonneborn-berger';
const table = tournament.standings([buchholz, sonnebornBerger]);
// [{ player: 'alice', rank: 1, score: 2.5, tiebreaks: [7.5, 6.25] }, ...]Tiebreak functions conform to:
type Tiebreak = (
playerId: string,
games: Game[][],
players: Player[],
) => number;Serialize and restore tournament state. The pairing system function must be re-provided when restoring, since functions aren't JSON-serializable.
const snapshot = tournament.toJSON();
const json = JSON.stringify(snapshot);
// Later...
const restored = Tournament.fromJSON(JSON.parse(json), dutch);
const nextRound = restored.pairRound();function bakuAcceleration(players: Player[]): AccelerationMethod;Returns an AccelerationMethod implementing
FIDE C.04.7 Baku Acceleration.
Splits players into two groups (GA = top half, GB = rest) and adds virtual points to GA players' scores in the first rounds, causing stronger players to face each other earlier.
import { Tournament, bakuAcceleration } from '@echecs/tournament';
import { dutch } from '@echecs/swiss';
const tournament = new Tournament({
acceleration: bakuAcceleration(players),
pairingSystem: dutch,
players,
rounds: 9,
});Virtual points:
- First half of accelerated rounds: GA players get 1 point
- Second half of accelerated rounds: GA players get 0.5 points
- After accelerated rounds: 0 points
- GB players: always 0 points
Virtual points affect pairing only — they are never stored in the game history or reflected in standings.
Any function matching the PairingSystem signature works:
type PairingSystem = (players: Player[], games: Game[][]) => PairingResult;| Package | Functions | FIDE Rules |
|---|---|---|
@echecs/swiss |
dutch, dubov, burstein, lim, doubleSwiss, swissTeam |
C.04.3, C.04.4.1-3, C.04.5, C.04.6 |
@echecs/round-robin |
roundRobin |
C.05 |
interface Player {
id: string;
rating?: number;
}
interface Game {
black: string;
kind?: GameKind; // optional: classifies unplayed rounds
result: Result;
white: string;
}
type GameKind = 'forfeit' | 'normal' | 'rated' | 'unrated';
type Result = 0 | 0.5 | 1;
interface Pairing {
black: string;
white: string;
}
interface Bye {
player: string;
}
interface PairingResult {
byes: Bye[];
pairings: Pairing[];
}
interface Standing {
player: string;
rank: number;
score: number;
tiebreaks: number[];
}
type Tiebreak = (
playerId: string,
games: Game[][],
players: Player[],
) => number;MIT