Comparing version 0.2.0 to 0.3.0
@@ -0,4 +1,8 @@ | ||
var TournamentType = /* @__PURE__ */ ((TournamentType2) => { | ||
TournamentType2["SWISS"] = "SWISS"; | ||
return TournamentType2; | ||
})(TournamentType || {}); | ||
class Results { | ||
pairings = /* @__PURE__ */ new Map(); | ||
constructor(results) { | ||
constructor(tournamentType, results) { | ||
this.tournamentType = tournamentType; | ||
for (const [roundIndex, roundResults] of results.entries()) { | ||
@@ -27,2 +31,3 @@ for (const pairing of roundResults.pairings) { | ||
} | ||
pairings = /* @__PURE__ */ new Map(); | ||
addToMap(playerId, round, result) { | ||
@@ -67,2 +72,3 @@ if (!this.pairings.has(playerId)) { | ||
Results, | ||
TournamentType, | ||
isForfeitLoss, | ||
@@ -69,0 +75,0 @@ isPaired, |
@@ -33,2 +33,5 @@ export type Score = 0 | 0.5 | 1; | ||
}; | ||
export declare enum TournamentType { | ||
SWISS = "SWISS" | ||
} | ||
/** | ||
@@ -38,4 +41,5 @@ * Immutable representation of a tournament's pairings and results. | ||
export declare class Results { | ||
readonly tournamentType: TournamentType; | ||
private pairings; | ||
constructor(results: RoundResults[]); | ||
constructor(tournamentType: TournamentType, results: RoundResults[]); | ||
private addToMap; | ||
@@ -42,0 +46,0 @@ /** |
{ | ||
"name": "tiebreak", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Calculation of chess tournament tiebreaks compliant with FIDE regulations", | ||
@@ -27,4 +27,4 @@ "keywords": [ | ||
"engines": { | ||
"node": "^18.15.0", | ||
"npm": "^9.5.0" | ||
"node": ">=18.15.0", | ||
"npm": ">=9.5.0" | ||
}, | ||
@@ -31,0 +31,0 @@ "volta": { |
@@ -28,8 +28,50 @@ # Tiebreak: FIDE compliant tournament tiebreak calculation | ||
TODO | ||
`npm install --save tiebreak` | ||
## Usage | ||
TODO | ||
First, you need to create a `Results` object with all relevant pairing information. You can use an arbitrary string or number to identify players. | ||
```typescript | ||
import { Results, TournamentType } from "tiebreak" | ||
const results = new Results(TournamentType.SWISS, [ | ||
{ | ||
// Example round 1: Player D did not show up | ||
pairings: [ | ||
{ white: "C", black: "A", scoreWhite: 0.5, scoreBlack: 0.5, forfeited: false }, | ||
{ white: "B", black: "D", scoreWhite: 1, scoreBlack: 0, forfeited: true }, | ||
], | ||
}, | ||
{ | ||
// Example round 2: Player D was not paired, player C received a bye | ||
pairings: [{ white: "A", black: "B", scoreWhite: 0, scoreBlack: 1, forfeited: false }], | ||
pairingAllocatedByes: ["C"], | ||
}, | ||
]) | ||
``` | ||
You can now calculate a specific tiebreak score for a given player and round like this: | ||
```typescript | ||
const tiebreaker = new Tiebreaker(results, UnplayedRoundsAdjustment.FIDE_2023) | ||
console.log(tiebreaker.buchholz("A", 2)) | ||
// Output: 3.5 | ||
``` | ||
Or you can just calculate an entire ranking with multiple tiebreakers: | ||
```typescript | ||
console.log(tiebreaker.ranking(2, [Tiebreak.SCORE, Tiebreak.BUCHHOLZ])) | ||
/* | ||
Output: | ||
[ | ||
{ rank: 1, playerId: "B", scores: [2, 2.5] }, | ||
{ rank: 2, playerId: "C", scores: [1.5, 2] }, | ||
{ rank: 3, playerId: "A", scores: [0.5, 3.5] }, | ||
{ rank: 4, playerId: "D", scores: [0, 0] }, | ||
] | ||
*/ | ||
``` | ||
## Project Goals | ||
@@ -55,5 +97,5 @@ | ||
- Increase version | ||
- Update CHANGELOG | ||
- Push to master and ensure all workflows are passing | ||
- Update CHANGELOG (or create it) | ||
- Push to main and ensure all workflows are passing | ||
- `npm run build` | ||
- `npm publish` |
import { describe, expect, it } from "vitest" | ||
import { Results, RoundResults, Score } from "../results.js" | ||
import { Results, RoundResults, Score, TournamentType } from "../results.js" | ||
import { Modifier, Tiebreak, Tiebreaker, UnplayedRoundsAdjustment } from "../tiebreak.js" | ||
@@ -10,3 +10,3 @@ import { readTestCases } from "./util/test-case-reader.js" | ||
it("should sum score of all pairings", () => { | ||
const results = new Results([ | ||
const results = new Results(TournamentType.SWISS, [ | ||
round(["A:B 1:0"]), | ||
@@ -35,3 +35,3 @@ round(["C:A 0:1 forfeit"]), | ||
const tiebreak = new Tiebreaker( | ||
new Results([ | ||
new Results(TournamentType.SWISS, [ | ||
{ pairings: [], pairingAllocatedByes: ["A"] }, | ||
@@ -53,3 +53,3 @@ { pairings: [], halfPointByes: ["A"] }, | ||
it("should sum points of opponents", () => { | ||
const rounds = new Results([ | ||
const rounds = new Results(TournamentType.SWISS, [ | ||
round(["A:B 1:0"]), | ||
@@ -129,3 +129,3 @@ round(["C:A 0.5:0.5"]), | ||
it("should count any unplayed games of opponents as draw", () => { | ||
const rounds = new Results([ | ||
const rounds = new Results(TournamentType.SWISS, [ | ||
round(["A:B 1:0", "C:Cx 1:0", "D:Dx 1:0", "E:Ex 1:0"]), | ||
@@ -145,3 +145,6 @@ round(["B:X 1:0 forfeit", "A:C 1:0", "D:Dy 1:0", "E:Ey 1:0"]), | ||
it("should use virtual opponents for unplayed games", () => { | ||
const rounds = new Results([round(["A:B 1:0"]), round(["A:B 1:0 forfeit"])]) | ||
const rounds = new Results(TournamentType.SWISS, [ | ||
round(["A:B 1:0"]), | ||
round(["A:B 1:0 forfeit"]), | ||
]) | ||
const tiebreak = new Tiebreaker(rounds, UnplayedRoundsAdjustment.FIDE_2009) | ||
@@ -175,3 +178,3 @@ | ||
it("should rank simple tournament", () => { | ||
const results = new Results([ | ||
const results = new Results(TournamentType.SWISS, [ | ||
{ | ||
@@ -200,3 +203,3 @@ // Round 1: Player D did not show up | ||
it("should return tied players on the same rank", () => { | ||
const results = new Results([ | ||
const results = new Results(TournamentType.SWISS, [ | ||
{ | ||
@@ -203,0 +206,0 @@ // Round 1: Player D did not show up |
import { promises as fs } from "fs" | ||
import { parse } from "csv-parse/sync" | ||
import { Results, RoundResults, Score } from "../../results.js" | ||
import { Results, RoundResults, Score, TournamentType } from "../../results.js" | ||
@@ -10,3 +10,3 @@ export async function readTestCases(filename: string): Promise<Results> { | ||
const rounds = parseRounds(csv) | ||
return new Results(rounds) | ||
return new Results(TournamentType.SWISS, rounds) | ||
} | ||
@@ -13,0 +13,0 @@ |
@@ -39,2 +39,7 @@ export type Score = 0 | 0.5 | 1 | ||
export enum TournamentType { | ||
// TODO: Add support for round robin tournaments (where no unplayed games adjustments are made). | ||
SWISS = "SWISS", | ||
} | ||
/** | ||
@@ -46,3 +51,6 @@ * Immutable representation of a tournament's pairings and results. | ||
constructor(results: RoundResults[]) { | ||
constructor( | ||
public readonly tournamentType: TournamentType, | ||
results: RoundResults[], | ||
) { | ||
for (const [roundIndex, roundResults] of results.entries()) { | ||
@@ -49,0 +57,0 @@ for (const pairing of roundResults.pairings) { |
@@ -77,3 +77,2 @@ import { | ||
*/ | ||
// TODO: Rename to Tiebreaker | ||
// TODO: Measure performance for large tournaments. Add caching/memoization if needed. | ||
@@ -80,0 +79,0 @@ export class Tiebreaker { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
69636
1232
100
0