diff --git a/eslint.config.mjs b/eslint.config.mjs index a62cf44..d9f9f6f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -92,6 +92,10 @@ export default typescript.config( }, ], '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_' }, + ], }, settings: { 'import-x/resolver': { diff --git a/package.json b/package.json index d878fcf..1c90b34 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ }, "description": "Progressive score tiebreak for chess tournaments following FIDE rules. Zero dependencies.", "peerDependencies": { - "@echecs/tournament": "^2.0.0" + "@echecs/tournament": "^3.0.0" }, "devDependencies": { - "@echecs/tournament": "^2.1.2", + "@echecs/tournament": "^3.1.0", "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.57.0", "@vitest/coverage-v8": "^4.1.0", @@ -83,5 +83,5 @@ }, "sideEffects": false, "type": "module", - "version": "3.0.2" + "version": "4.0.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d3c75a..c1c0630 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@echecs/tournament': - specifier: ^2.1.2 - version: 2.1.2 + specifier: ^3.1.0 + version: 3.1.0 '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.3.0) @@ -107,9 +107,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@echecs/tournament@2.1.2': - resolution: {integrity: sha512-DYq8Nbc1Dq15ZpQMKWziGmfyxd4RC0jIgWsbVRLM6/q+sPKPjSPxiBtRC8+u5gPr0lKaK5eoZKs295I/W60akA==} - engines: {node: '>=20'} + '@echecs/tournament@3.1.0': + resolution: {integrity: sha512-eXc0BMjpimolwpcPnuzEClyFZ90TnN2nLEJLJ7UCr4oFCstjI2lUbABnLE7HsKUYg+j7QadgtTR2J+wcflDlwg==} + engines: {node: '>=22'} '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -1817,7 +1817,7 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@echecs/tournament@2.1.2': {} + '@echecs/tournament@3.1.0': {} '@emnapi/core@1.10.0': dependencies: diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index e1f3f18..0ac72e4 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -3,51 +3,55 @@ import { describe, expect, it } from 'vitest'; import { progressiveCut1 } from '../cut1.js'; import { progressive } from '../index.js'; -import type { Game } from '@echecs/tournament'; - -// 4 players, 3 rounds: -// Round 1: A(W) 1-0 B, C(W) 0-1 D -// Round 2: A(W) 0.5-0.5 D, C(W) 0-1 B -// Round 3: A(W) 1-0 C, D(W) 1-0 B -// Scores: A=2.5, D=2.5, B=1, C=0 - -const GAMES: Game[][] = [ - [ - { black: 'B', result: 1, white: 'A' }, - { black: 'D', result: 0, white: 'C' }, - ], - [ - { black: 'D', result: 0.5, white: 'A' }, - { black: 'B', result: 0, white: 'C' }, - ], - [ - { black: 'C', result: 1, white: 'A' }, - { black: 'B', result: 1, white: 'D' }, - ], +import type { CompletedRound, Player } from '@echecs/tournament'; + +const PLAYERS: Player[] = [ + { id: 'A', points: 2.5, rank: 1 }, + { id: 'B', points: 1, rank: 3 }, + { id: 'C', points: 0, rank: 4 }, + { id: 'D', points: 2.5, rank: 2 }, +]; + +const ROUNDS: CompletedRound[] = [ + { + byes: [], + games: [ + { black: 'B', result: 'white', white: 'A' }, + { black: 'D', result: 'black', white: 'C' }, + ], + }, + { + byes: [], + games: [ + { black: 'D', result: 'draw', white: 'A' }, + { black: 'B', result: 'black', white: 'C' }, + ], + }, + { + byes: [], + games: [ + { black: 'C', result: 'white', white: 'A' }, + { black: 'B', result: 'white', white: 'D' }, + ], + }, ]; describe('progressive', () => { it('returns sum of cumulative scores after each round', () => { - // A: R1=1 (cum=1), R2=0.5 (cum=1.5), R3=1 (cum=2.5) - // sum = 1 + 1.5 + 2.5 = 5 - expect(progressive('A', GAMES)).toBe(5); + expect(progressive('A', ROUNDS, PLAYERS)).toBe(5); }); it('handles player with no games', () => { - expect(progressive('A', [])).toBe(0); + expect(progressive('A', [], PLAYERS)).toBe(0); }); }); describe('progressiveCut1', () => { it('sums cumulative scores from round 2 onwards', () => { - // A from round 2: running sum starting after R1: - // After R1: cum=1; R2: cum=1.5 → add 1.5; R3: cum=2.5 → add 2.5 → total=4 - // BUT per spec: recalculate from round 2: - // cum starts at 0 from R2: R2=0.5(cum=0.5), R3=1(cum=1.5) → sum=0.5+1.5=2 - expect(progressiveCut1('A', GAMES)).toBe(2); + expect(progressiveCut1('A', ROUNDS, PLAYERS)).toBe(2); }); it('handles player with no games', () => { - expect(progressiveCut1('A', [])).toBe(0); + expect(progressiveCut1('A', [], PLAYERS)).toBe(0); }); }); diff --git a/src/cut1.ts b/src/cut1.ts index 430d5ec..035679f 100644 --- a/src/cut1.ts +++ b/src/cut1.ts @@ -1,12 +1,16 @@ import { playerResult } from './utilities.js'; -import type { Game } from '@echecs/tournament'; +import type { CompletedRound, Player } from '@echecs/tournament'; -function progressiveCut1(player: string, games: Game[][]): number { +function progressiveCut1( + player: string, + rounds: CompletedRound[], + _players: Player[], +): number { let cumulative = 0; let total = 0; - for (const round of games.slice(1)) { - cumulative += playerResult(player, round); + for (const round of rounds.slice(1)) { + cumulative += playerResult(player, round.games); total += cumulative; } return total; @@ -14,4 +18,10 @@ function progressiveCut1(player: string, games: Game[][]): number { export { progressiveCut1, progressiveCut1 as tiebreak }; -export type { Game, GameKind, Player, Result } from '@echecs/tournament'; +export type { + Bye, + CompletedRound, + Game, + Pairing, + Player, +} from '@echecs/tournament'; diff --git a/src/index.ts b/src/index.ts index 2373aa8..2b9f853 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,16 @@ import { playerResult } from './utilities.js'; -import type { Game } from '@echecs/tournament'; +import type { CompletedRound, Player } from '@echecs/tournament'; -function progressive(player: string, games: Game[][]): number { +function progressive( + player: string, + rounds: CompletedRound[], + _players: Player[], +): number { let cumulative = 0; let total = 0; - for (const round of games) { - cumulative += playerResult(player, round); + for (const round of rounds) { + cumulative += playerResult(player, round.games); total += cumulative; } return total; @@ -14,4 +18,10 @@ function progressive(player: string, games: Game[][]): number { export { progressive, progressive as tiebreak }; -export type { Game, GameKind, Player, Result } from '@echecs/tournament'; +export type { + Bye, + CompletedRound, + Game, + Pairing, + Player, +} from '@echecs/tournament'; diff --git a/src/utilities.ts b/src/utilities.ts index 4b8923d..5514f95 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,19 +1,31 @@ -import type { Game } from '@echecs/tournament'; +import type { CompletedRound, Game } from '@echecs/tournament'; -function gamesForPlayer(player: string, games: Game[][]): Game[] { - return games.flat().filter((g) => g.white === player || g.black === player); +function gamesForPlayer(player: string, rounds: CompletedRound[]): Game[] { + return rounds + .flatMap((r) => r.games) + .filter((g) => g.white === player || g.black === player); +} + +function scoreFor(player: string, game: Game): number { + if (game.result === 'draw') { + return 0.5; + } + if (game.result === 'none') { + return 0; + } + return (game.result === 'white' && game.white === player) || + (game.result === 'black' && game.black === player) + ? 1 + : 0; } function playerResult(player: string, round: Game[]): number { for (const g of round) { - if (g.white === player) { - return g.result; - } - if (g.black === player) { - return 1 - g.result; + if (g.white === player || g.black === player) { + return scoreFor(player, g); } } return 0; } -export { gamesForPlayer, playerResult }; +export { gamesForPlayer, playerResult, scoreFor };