{"id":51319379,"url":"https://github.com/echecsjs/tournament","last_synced_at":"2026-07-01T11:04:11.056Z","repository":{"id":346169099,"uuid":"1188782834","full_name":"echecsjs/tournament","owner":"echecsjs","description":"Stateful chess tournament orchestrator for any FIDE pairing system. Supports Swiss, round-robin, and accelerated formats. Zero dependencies.","archived":false,"fork":false,"pushed_at":"2026-06-29T13:57:18.000Z","size":1613,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-29T15:23:30.096Z","etag":null,"topics":["chess","fide","pairing","tournament","typescript"],"latest_commit_sha":null,"homepage":"https://tournament.echecs.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/echecsjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-22T15:24:27.000Z","updated_at":"2026-06-29T13:58:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/echecsjs/tournament","commit_stats":null,"previous_names":["mormubis/tournament","echecsjs/tournament"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/echecsjs/tournament","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echecsjs%2Ftournament","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echecsjs%2Ftournament/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echecsjs%2Ftournament/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echecsjs%2Ftournament/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/echecsjs","download_url":"https://codeload.github.com/echecsjs/tournament/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echecsjs%2Ftournament/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35003466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chess","fide","pairing","tournament","typescript"],"created_at":"2026-07-01T11:04:10.285Z","updated_at":"2026-07-01T11:04:11.041Z","avatar_url":"https://github.com/echecsjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tournament\n\n[![npm](https://img.shields.io/npm/v/@echecs/tournament)](https://www.npmjs.com/package/@echecs/tournament)\n[![Coverage](https://codecov.io/gh/echecsjs/tournament/branch/main/graph/badge.svg)](https://codecov.io/gh/echecsjs/tournament)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Spec](https://img.shields.io/badge/Spec-FIDE-green.svg)](SPEC.md)\n\n**Tournament** is a TypeScript library for orchestrating chess tournaments using\nany FIDE pairing system. It provides a stateful `Tournament` class that manages\nthe full lifecycle — pairing rounds, recording results, computing standings —\nand a `bakuAcceleration()` function implementing FIDE C.04.7. Zero runtime\ndependencies.\n\nThe pairing system is injected as a function parameter. Bring your own from\n[`@echecs/swiss`](https://www.npmjs.com/package/@echecs/swiss),\n[`@echecs/round-robin`](https://www.npmjs.com/package/@echecs/round-robin), or a\ncustom implementation.\n\n## Installation\n\n```bash\nnpm install @echecs/tournament\n```\n\n## Quick Start\n\n```typescript\nimport { Tournament } from '@echecs/tournament';\nimport { pair } from '@echecs/swiss/dutch';\nimport type { Game, GameKind, Player, Tiebreak } from '@echecs/tournament';\n\nconst players: Player[] = [\n  { id: 'alice', rating: 2100 },\n  { id: 'bob', rating: 1950 },\n  { id: 'carol', rating: 1870 },\n  { id: 'dave', rating: 1820 },\n];\n\nconst tournament = new Tournament({\n  pairingSystem: pair,\n  players,\n  rounds: 3,\n});\n\n// Round 1\nconst round1 = tournament.pairRound();\n// round1.pairings = [{ black: 'carol', white: 'alice' }, ...]\n// round1.byes    = [{ player: 'dave' }]\n\ntournament.recordResult({ black: 'carol', result: 1, white: 'alice' });\ntournament.recordResult({ black: 'dave', result: 0.5, white: 'bob' });\n\n// Round 2\nconst round2 = tournament.pairRound();\n// ...record results...\n\n// Standings (no tiebreaks)\nconst table = tournament.standings();\n// [{ player: 'alice', rank: 1, score: 2, tiebreaks: [] }, ...]\n```\n\n## API\n\n### `Tournament`\n\n```typescript\nclass Tournament {\n  constructor(options: TournamentOptions);\n\n  clearResult(round: number, white: string, black: string): void;\n  pairRound(): PairingResult;\n  recordResult(game: Game): void;\n  standings(tiebreaks?: Tiebreak[]): Standing[];\n  updateResult(round: number, game: Game): void;\n\n  get currentRound(): number;\n  get games(): readonly (readonly Game[])[];\n  get isComplete(): boolean;\n  get players(): readonly Player[];\n  get rounds(): number;\n  get tiebreaks(): readonly string[];\n\n  toJSON(): TournamentSnapshot;\n  static fromJSON(\n    snapshot: TournamentSnapshot,\n    pairingSystem: PairingSystem,\n    acceleration?: AccelerationMethod,\n  ): Tournament;\n}\n```\n\n#### `constructor(options)`\n\nCreates a new tournament.\n\n```typescript\ninterface TournamentOptions {\n  acceleration?: AccelerationMethod; // e.g. bakuAcceleration(players)\n  pairingSystem: PairingSystem; // e.g. pair from @echecs/swiss/dutch\n  players: Player[]; // all participants\n  rounds: number; // total number of rounds\n  tiebreaks?: string[]; // opaque IDs preserved through serialization\n}\n```\n\nThrows `RangeError` if fewer than 2 players or fewer than 1 round.\n\n#### `pairRound()`\n\nGenerates pairings for the next round using the injected pairing system. Returns\na `PairingResult` with pairings and byes.\n\nThrows `RangeError` if the tournament is complete or the current round has\nunrecorded results.\n\n#### `clearResult(round, white, black)`\n\nRemoves a previously recorded result, identified by round number and player\npair. The lookup checks both color orderings, so the caller can pass `white` and\n`black` in either order.\n\n```typescript\ntournament.clearResult(1, 'alice', 'carol');\n```\n\nAfter clearing, the round becomes incomplete and the pairing can be re-recorded\nvia `recordResult`.\n\nThrows `RangeError` if the round is invalid or no matching result exists.\n\n#### `recordResult(game)`\n\nRecords a game result for the current round.\n\n```typescript\ntournament.recordResult({\n  black: 'carol',\n  result: 1, // 1 = white wins, 0.5 = draw, 0 = black wins\n  white: 'alice',\n});\n```\n\nThe optional `kind?: GameKind` field classifies the game type. When provided,\nthe result must be consistent with the kind (see\n[GameKind validation](#gamekind-validation) below).\n\n```typescript\ntournament.recordResult({\n  black: 'carol',\n  kind: 'forfeit-win',\n  result: 1,\n  white: 'alice',\n});\n```\n\nThrows `RangeError` if the players don't match any pairing in the current round,\nor if `kind` and `result` are inconsistent.\n\n#### `updateResult(round, game)`\n\nReplaces an existing result in any round. The game is identified by the\n`white`/`black` player pair (checked in both orderings). The stored game retains\nits original color assignment.\n\n```typescript\n// Change round 1 result from white-wins to draw\ntournament.updateResult(1, {\n  black: 'carol',\n  result: 0.5,\n  white: 'alice',\n});\n\n// Add a kind to an existing result\ntournament.updateResult(1, {\n  black: 'carol',\n  kind: 'forfeit-win',\n  result: 1,\n  white: 'alice',\n});\n```\n\nThrows `RangeError` if the round is invalid, no matching result exists, or\n`kind` and `result` are inconsistent.\n\n#### `standings(tiebreaks?)`\n\nReturns players ranked by score, with optional tiebreaks applied in order. Each\ntiebreak function receives `(playerId, games, players)` and returns a number.\n\n```typescript\nimport { buchholz } from '@echecs/buchholz';\nimport { sonnebornBerger } from '@echecs/sonneborn-berger';\n\nconst table = tournament.standings([buchholz, sonnebornBerger]);\n// [{ player: 'alice', rank: 1, score: 2.5, tiebreaks: [7.5, 6.25] }, ...]\n```\n\nTiebreak functions conform to:\n\n```typescript\ntype Tiebreak = (\n  playerId: string,\n  games: Game[][],\n  players: Player[],\n) =\u003e number;\n```\n\n#### `toJSON()` / `fromJSON()`\n\nSerialize and restore tournament state. The pairing system function must be\nre-provided when restoring, since functions aren't JSON-serializable.\n\n```typescript\nconst snapshot = tournament.toJSON();\nconst json = JSON.stringify(snapshot);\n\n// Later...\nconst restored = Tournament.fromJSON(JSON.parse(json), pair);\nconst nextRound = restored.pairRound();\n```\n\n### `bakuAcceleration(players)`\n\n```typescript\nfunction bakuAcceleration(players: Player[]): AccelerationMethod;\n```\n\nReturns an `AccelerationMethod` implementing\n[FIDE C.04.7 Baku Acceleration](https://handbook.fide.com/chapter/C0407202602).\n\nSplits players into two groups (GA = top half, GB = rest) and adds virtual\npoints to GA players' scores in the first rounds, causing stronger players to\nface each other earlier.\n\n```typescript\nimport { Tournament, bakuAcceleration } from '@echecs/tournament';\nimport { pair } from '@echecs/swiss/dutch';\n\nconst tournament = new Tournament({\n  acceleration: bakuAcceleration(players),\n  pairingSystem: pair,\n  players,\n  rounds: 9,\n});\n```\n\nVirtual points:\n\n- **First half of accelerated rounds**: GA players get 1 point\n- **Second half of accelerated rounds**: GA players get 0.5 points\n- **After accelerated rounds**: 0 points\n- **GB players**: always 0 points\n\nVirtual points affect pairing only — they are never stored in the game history\nor reflected in standings.\n\n## Compatible Pairing Systems\n\nAny function matching the `PairingSystem` signature works:\n\n```typescript\ntype PairingSystem = (players: Player[], games: Game[][]) =\u003e PairingResult;\n```\n\n| Package                                                                    | Subpath                  | FIDE Rules |\n| -------------------------------------------------------------------------- | ------------------------ | ---------- |\n| [`@echecs/swiss`](https://www.npmjs.com/package/@echecs/swiss)             | `@echecs/swiss/dutch`    | C.04.3     |\n|                                                                            | `@echecs/swiss/dubov`    | C.04.4.1   |\n|                                                                            | `@echecs/swiss/burstein` | C.04.4.2   |\n|                                                                            | `@echecs/swiss/lim`      | C.04.4.3   |\n|                                                                            | `@echecs/swiss/double`   | C.04.5     |\n|                                                                            | `@echecs/swiss/team`     | C.04.6     |\n| [`@echecs/round-robin`](https://www.npmjs.com/package/@echecs/round-robin) | `@echecs/round-robin`    | C.05       |\n\nAll subpaths export a `pair` function conforming to the `PairingSystem`\nsignature.\n\n## Types\n\n```typescript\ninterface Player {\n  id: string;\n  rating?: number;\n}\n\ninterface Game {\n  black: string;\n  kind?: GameKind; // optional: classifies unplayed rounds\n  result: Result;\n  white: string;\n}\n\ntype GameKind =\n  | 'forfeit-loss'\n  | 'forfeit-win'\n  | 'full-bye'\n  | 'half-bye'\n  | 'pairing-bye'\n  | 'zero-bye';\n\ntype Result = 0 | 0.5 | 1;\n\ninterface Pairing {\n  black: string;\n  white: string;\n}\n\ninterface Bye {\n  player: string;\n}\n\ninterface PairingResult {\n  byes: Bye[];\n  pairings: Pairing[];\n}\n\ninterface Standing {\n  player: string;\n  rank: number;\n  score: number;\n  tiebreaks: number[];\n}\n\ntype Tiebreak = (\n  playerId: string,\n  games: Game[][],\n  players: Player[],\n) =\u003e number;\n\ntype PairingSystem = (players: Player[], games: Game[][]) =\u003e PairingResult;\n\ninterface AccelerationMethod {\n  virtualPoints: (player: Player, round: number, totalRounds: number) =\u003e number;\n}\n\ninterface TournamentSnapshot {\n  currentRound: number;\n  games: Game[][];\n  players: Player[];\n  roundPairings: Record\u003cstring, PairingResult\u003e;\n  rounds: number;\n  tiebreaks?: string[];\n}\n```\n\n## GameKind Validation\n\nWhen `kind` is provided in `recordResult` or `updateResult`, the `result` must\nmatch. Mismatches throw `RangeError`.\n\n| `kind`         | Required `result` | FIDE ref    |\n| -------------- | ----------------- | ----------- |\n| `forfeit-win`  | `1`               | Art. 16.2.2 |\n| `forfeit-loss` | `0`               | Art. 16.2.4 |\n| `full-bye`     | `1`               | —           |\n| `half-bye`     | `0.5`             | Art. 16.2.5 |\n| `pairing-bye`  | `1`               | Art. 16.2.1 |\n| `zero-bye`     | `0`               | Art. 16.2.3 |\n\nWhen `kind` is omitted, any result is accepted.\n\n## FIDE References\n\n- [C.07 Play-Off and Tie-Break Regulations](https://handbook.fide.com/chapter/TieBreakRegulations032026)\n- [C.04.7 Baku Acceleration](https://handbook.fide.com/chapter/C0407202602)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fechecsjs%2Ftournament","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fechecsjs%2Ftournament","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fechecsjs%2Ftournament/lists"}