@echecs/tournament

中文文档
3.3.0发布于 2026/05/28MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 3139

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

原始使用文档
文档目录10 节
  1. Installation
  2. Quick Start
  3. API
  4. Tournament
  5. bakuAcceleration(players)
  6. Compatible Pairing Systems
  7. Types
  8. GameKind Validation
  9. FIDE References
  10. License

Tournament

npm Coverage License: MIT Spec

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 { pair } from '@echecs/swiss/dutch';
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: pair,
  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);

  clearResult(round: number, white: string, black: string): void;
  pairRound(): PairingResult;
  recordResult(game: Game): void;
  standings(tiebreaks?: Tiebreak[]): Standing[];
  updateResult(round: number, game: Game): void;

  get currentRound(): number;
  get games(): readonly (readonly Game[])[];
  get isComplete(): boolean;
  get players(): readonly Player[];
  get rounds(): number;
  get tiebreaks(): readonly string[];

  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. pair from @echecs/swiss/dutch
  players: Player[]; // all participants
  rounds: number; // total number of rounds
  tiebreaks?: string[]; // opaque IDs preserved through serialization
}

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.

clearResult(round, white, black)

Removes a previously recorded result, identified by round number and player pair. The lookup checks both color orderings, so the caller can pass white and black in either order.

tournament.clearResult(1, 'alice', 'carol');

After clearing, the round becomes incomplete and the pairing can be re-recorded via recordResult.

Throws RangeError if the round is invalid or no matching result exists.

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. When provided, the result must be consistent with the kind (see GameKind validation below).

tournament.recordResult({
  black: 'carol',
  kind: 'forfeit-win',
  result: 1,
  white: 'alice',
});

Throws RangeError if the players don't match any pairing in the current round, or if kind and result are inconsistent.

updateResult(round, game)

Replaces an existing result in any round. The game is identified by the white/black player pair (checked in both orderings). The stored game retains its original color assignment.

// Change round 1 result from white-wins to draw
tournament.updateResult(1, {
  black: 'carol',
  result: 0.5,
  white: 'alice',
});

// Add a kind to an existing result
tournament.updateResult(1, {
  black: 'carol',
  kind: 'forfeit-win',
  result: 1,
  white: 'alice',
});

Throws RangeError if the round is invalid, no matching result exists, or kind and result are inconsistent.

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), pair);
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 { pair } from '@echecs/swiss/dutch';

const tournament = new Tournament({
  acceleration: bakuAcceleration(players),
  pairingSystem: pair,
  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;
PackageSubpathFIDE Rules
@echecs/swiss@echecs/swiss/dutchC.04.3
@echecs/swiss/dubovC.04.4.1
@echecs/swiss/bursteinC.04.4.2
@echecs/swiss/limC.04.4.3
@echecs/swiss/doubleC.04.5
@echecs/swiss/teamC.04.6
@echecs/round-robin@echecs/round-robinC.05

All subpaths export a pair function conforming to the PairingSystem signature.

Types

interface Player {
  id: string;
  rating?: number;
}

interface Game {
  black: string;
  kind?: GameKind; // optional: classifies unplayed rounds
  result: Result;
  white: string;
}

type GameKind =
  | 'forfeit-loss'
  | 'forfeit-win'
  | 'full-bye'
  | 'half-bye'
  | 'pairing-bye'
  | 'zero-bye';

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;

type PairingSystem = (players: Player[], games: Game[][]) => PairingResult;

interface AccelerationMethod {
  virtualPoints: (player: Player, round: number, totalRounds: number) => number;
}

interface TournamentSnapshot {
  currentRound: number;
  games: Game[][];
  players: Player[];
  roundPairings: Record<string, PairingResult>;
  rounds: number;
  tiebreaks?: string[];
}

GameKind Validation

When kind is provided in recordResult or updateResult, the result must match. Mismatches throw RangeError.

kindRequired resultFIDE ref
forfeit-win1Art. 16.2.2
forfeit-loss0Art. 16.2.4
full-bye1—
half-bye0.5Art. 16.2.5
pairing-bye1Art. 16.2.1
zero-bye0Art. 16.2.3

When kind is omitted, any result is accepted.

FIDE References

License

MIT

版本与发布记录

@echecs/tournament 共发布 10 个版本,首次发布于 2026/03/22;过去 12 个月发布了 10 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.3.0latest2026/05/285 个月前
3.2.02026/05/265 个月前
3.1.12026/05/155 个月前
3.1.02026/05/096 个月前
3.0.02026/05/096 个月前
2.1.22026/04/186 个月前

关于 @echecs/tournament 的常见问题

@echecs/tournament 是什么?

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

如何安装 @echecs/tournament?

在项目目录运行 npm install @echecs/tournament,或使用 pnpm add / yarn add / bun add。

@echecs/tournament 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@echecs/tournament 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@echecs/tournament 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@echecs/tournament 可以商用吗?

@echecs/tournament 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@echecs/tournament 的下载量有多少?

根据 npm 官方下载统计,@echecs/tournament 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,139 次,日均约 105 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@echecs/tournament 还在维护吗?

最新版本 3.3.0 发布于 2026/05/28(5 个月前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/03/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。