Skip to content

mormubis/tournament

Repository files navigation

Tournament

npm Test Coverage License: MIT

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.

Installation

npm install @echecs/tournament

Quick Start

import { 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: [] }, ...]

API

Tournament

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;
}

constructor(options)

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.

pairRound()

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.

recordResult(game)

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.

standings(tiebreaks?)

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;

toJSON() / fromJSON()

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();

bakuAcceleration(players)

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.

Compatible Pairing Systems

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

Types

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;

FIDE References

License

MIT

About

Stateful chess tournament orchestrator for any FIDE pairing system. Supports Swiss, round-robin, and accelerated formats. Zero dependencies.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors