Average Rating is a TypeScript library implementing the Average Rating of Opponents tiebreaks for chess tournaments, following the FIDE Tiebreak Regulations (sections 10.1 and 10.6). Zero runtime dependencies.
npm install @echecs/average-ratingimport { averageRatingOfOpponents, tiebreak } from '@echecs/average-rating';
import type { Game, GameKind, Player, Result } from '@echecs/average-rating';
const players: Player[] = [
{ id: 'A', rating: 1800 },
{ id: 'B', rating: 1600 },
{ id: 'C', rating: 1700 },
{ id: 'D', rating: 1900 },
];
// games[n] = round n+1; Game has no `round` field
const games: Game[][] = [
[{ black: 'B', result: 1, white: 'A' }], // round 1
[{ black: 'C', result: 0.5, white: 'A' }], // round 2
[{ black: 'A', result: 0, white: 'D' }], // round 3
// Byes excluded from ARO regardless of kind
[{ black: '', kind: 'half-bye', result: 0.5, white: 'A' }], // round 4
];
const avg = averageRatingOfOpponents('A', games, players);
// Returns Math.round((1600 + 1700 + 1900) / 3) = 1733
// `tiebreak` is an alias for `averageRatingOfOpponents`
const same = tiebreak('A', games, players);import {
averageRatingOfOpponentsCut1,
tiebreak,
} from '@echecs/average-rating/cut1';
const avg = averageRatingOfOpponentsCut1('A', games, players);
// Returns Math.round((1700 + 1900) / 2) = 1800
// (lowest-rated opponent, B at 1600, is excluded)All functions share the same signature:
(playerId: string, games: Game[][], players: Player[]): numberPlayer.rating is optional — players without a rating are silently skipped.
Functions return 0 when no rated opponents have been faced. Round is
determined by array position: games[0] = round 1, games[1] = round 2, etc.
The Game type has no round field. The optional kind?: GameKind field on
Game classifies unplayed rounds; byes are excluded from all ARO calculations.
interface Player {
id: string;
rating?: number; // optional — unrated players are skipped
}interface Game {
black: string;
kind?: GameKind;
result: Result;
white: string;
}type Result = 0 | 0.5 | 1;type GameKind =
| 'forfeit-loss'
| 'forfeit-win'
| 'full-bye'
| 'half-bye'
| 'pairing-bye'
| 'zero-bye';FIDE section 10.1 — Average FIDE rating of all opponents faced by
playerId. Byes are excluded. Opponents not found in players or without a
rating are skipped. Returns the rounded integer average.
tiebreak is an alias for averageRatingOfOpponents.
import { averageRatingOfOpponents, tiebreak } from '@echecs/average-rating';FIDE section 10.6 — Average rating of opponents minus the lowest-rated one.
Collects all opponent ratings, removes the single lowest, then returns the
rounded integer average of the remainder. Returns 0 if only one rated opponent
was faced.
tiebreak is an alias for averageRatingOfOpponentsCut1.
import {
averageRatingOfOpponentsCut1,
tiebreak,
} from '@echecs/average-rating/cut1';Contributions are welcome. Please open an issue at github.com/echecsjs/average-rating/issues.