@tungstenstudio/darts-handicap-calculator
v1.0.0
Published
Handicap calculator for x01 and cricket darts matches
Downloads
213
Readme
@tungstenstudio/darts-handicap-calculator
Handicap calculator for x01 and cricket darts matches. Uses pure ratio methods — no lookup tables, flat offsets, or tiered brackets.
Install
npm i @tungstenstudio/darts-handicap-calculator
# or
pnpm add @tungstenstudio/darts-handicap-calculator
# or
yarn add @tungstenstudio/darts-handicap-calculator
# or
bun add @tungstenstudio/darts-handicap-calculatorX01 Handicap
Scores are adjusted proportionally by the ratio of player averages (PPD), so both players need roughly the same number of darts to finish.
Formula: adjustedScore = baseScore × (weakerPPD ÷ strongerPPD), rounded to the nearest integer.
Quick Start
import { calculateX01Handicap } from '@tungstenstudio/darts-handicap-calculator';
const result = calculateX01Handicap({
baseScore: 501,
direction: 'negative',
averageType: 'ppd',
playerA: { value: 22, name: 'Alice' },
playerB: { value: 16, name: 'Bob' },
});
result.playerA.startScore; // 501 (stronger player keeps base)
result.playerB.startScore; // 364 (weaker player starts lower)
result.handicapPoints; // 137
result.playerA.estimatedDarts; // 23
result.playerB.estimatedDarts; // 23
result.playerA.name; // 'Alice'
result.capped; // falseWith a Handicap Cap
const result = calculateX01Handicap({
baseScore: 501,
direction: 'negative',
averageType: 'ppd',
playerA: { value: 20 },
playerB: { value: 10 },
maxHandicap: 150,
});
result.playerA.startScore; // 501
result.playerB.startScore; // 351 (capped — would be 251 without cap)
result.handicapPoints; // 150
result.capped; // trueWith Score Rounding
const result = calculateX01Handicap({
baseScore: 501,
direction: 'negative',
averageType: 'ppd',
playerA: { value: 17 },
playerB: { value: 12 },
roundTo: 25,
});
result.playerB.startScore; // 350 (354 rounded to nearest 25)
result.rounded; // trueWith Handicap Percentage
const result = calculateX01Handicap({
baseScore: 501,
direction: 'negative',
averageType: 'ppd',
playerA: { value: 20 },
playerB: { value: 10 },
handicapPercentage: 75,
});
result.playerB.startScore; // 313 (75% of 250-point gap applied)
result.handicapPoints; // 188
result.compressed; // trueAPI
calculateX01Handicap(input: X01HandicapInput): X01HandicapResult
Calculates handicapped start scores for two players.
interface X01HandicapInput {
baseScore: number; // The base game score (e.g. 301, 501, 701)
direction: Direction; // 'negative' | 'positive'
averageType: AverageType; // 'ppd' | '3da'
playerA: PlayerAverage;
playerB: PlayerAverage;
maxHandicap?: number; // Optional cap on the handicap spread
roundTo?: number; // Round adjusted score to nearest N (e.g. 5, 10, 25)
handicapPercentage?: number; // Apply only N% of the handicap spread (1–100, default: 100)
}
interface PlayerAverage {
value: number; // The player's average
name?: string; // Optional player name (passed through to result)
}Returns:
interface X01HandicapResult {
baseScore: number;
direction: Direction;
handicapPoints: number; // Absolute difference between start scores
capped: boolean; // Whether maxHandicap was applied
rounded: boolean; // Whether roundTo changed the score
compressed: boolean; // Whether handicapPercentage reduced the spread
playerA: PlayerResult;
playerB: PlayerResult;
}
interface PlayerResult {
startScore: number; // The handicapped start score
ppd: number; // Points per dart
threeDartAverage: number; // 3-dart average
estimatedDarts: number; // Estimated darts to finish (ceil(startScore / ppd))
name?: string; // Player name (if provided in input)
}Direction
The direction parameter controls which player's score is adjusted:
| Direction | Stronger player | Weaker player |
|-----------|----------------|---------------|
| 'negative' | Keeps the base score | Starts lower (reduced proportionally) |
| 'positive' | Starts higher (raised proportionally) | Keeps the base score |
Both directions produce the same relative handicap. Choose based on your league's convention:
- Negative is the most common — it keeps the strong player at the standard game score.
- Positive is useful when you want all players to start at at least the base score.
Cricket Handicap
The weaker player starts with "spot marks" — free marks toward closing numbers — so both players need roughly the same number of rounds to close all seven numbers.
Formula: spotMarks = 21 × (1 − weakerAvg ÷ strongerAvg), rounded to the nearest integer. The total of 21 represents 3 marks × 7 cricket numbers.
Quick Start
import { calculateCricketHandicap } from '@tungstenstudio/darts-handicap-calculator';
const result = calculateCricketHandicap({
averageType: 'mpr',
playerA: { value: 3.0, name: 'Alice' },
playerB: { value: 2.0, name: 'Bob' },
});
result.spotMarks; // 7
result.playerA.startingMarks; // 0 (stronger player starts at 0)
result.playerB.startingMarks; // 7 (weaker player gets spot marks)
result.playerA.estimatedRounds; // 7
result.playerB.estimatedRounds; // 7With a Handicap Cap
const result = calculateCricketHandicap({
averageType: 'mpr',
playerA: { value: 4.0 },
playerB: { value: 1.0 },
maxHandicap: 10,
});
result.spotMarks; // 10 (capped — would be 16 without cap)
result.capped; // trueWith Rounding Mode
const result = calculateCricketHandicap({
averageType: 'mpr',
playerA: { value: 2.5 },
playerB: { value: 2.0 },
rounding: 'ceil', // 'round' (default) | 'ceil' | 'floor'
});
result.spotMarks; // 5 (4.2 ceiled — would be 4 with default rounding)With Handicap Percentage
const result = calculateCricketHandicap({
averageType: 'mpr',
playerA: { value: 3.0 },
playerB: { value: 2.0 },
handicapPercentage: 50,
});
result.spotMarks; // 4 (50% of 7 spot marks)
result.compressed; // trueAPI
calculateCricketHandicap(input: CricketHandicapInput): CricketHandicapResult
Calculates spot marks for two cricket players.
interface CricketHandicapInput {
averageType: CricketAverageType; // 'mpr' | 'ppd'
playerA: PlayerAverage;
playerB: PlayerAverage;
maxHandicap?: number; // Optional cap on spot marks
rounding?: CricketRounding; // 'round' (default) | 'ceil' | 'floor'
handicapPercentage?: number; // Apply only N% of the spot marks (1–100, default: 100)
}Returns:
interface CricketHandicapResult {
spotMarks: number; // Marks spotted to the weaker player
capped: boolean; // Whether maxHandicap was applied
rounding: CricketRounding; // The rounding mode used
compressed: boolean; // Whether handicapPercentage reduced the spread
playerA: CricketPlayerResult;
playerB: CricketPlayerResult;
}
interface CricketPlayerResult {
startingMarks: number; // 0 for stronger player, spotMarks for weaker
marksNeeded: number; // 21 − startingMarks
average: number; // The player's average
averageType: CricketAverageType; // 'mpr' | 'ppd'
estimatedRounds: number; // ceil(marksNeeded / average)
name?: string; // Player name (if provided in input)
}Utility Functions
ppdTo3DA(ppd: number): number
Converts points-per-dart to 3-dart average.
ppdTo3DA(20); // 60threeDAToPPD(tda: number): number
Converts 3-dart average to points-per-dart.
threeDAToPPD(60); // 20Error Handling
The calculator throws HandicapError (extends Error) with a code property for invalid inputs:
import { calculateX01Handicap, HandicapError } from '@tungstenstudio/darts-handicap-calculator';
try {
calculateX01Handicap(input);
} catch (err) {
if (err instanceof HandicapError) {
console.error(err.code); // e.g. 'INVALID_PLAYER_A_AVERAGE'
console.error(err.message); // Human-readable description
}
}| Error Code | Cause |
|------------|-------|
| INVALID_BASE_SCORE | baseScore is not a finite positive number |
| INVALID_PLAYER_A_AVERAGE | Player A's average is not a finite positive number |
| INVALID_PLAYER_B_AVERAGE | Player B's average is not a finite positive number |
| INVALID_MAX_HANDICAP | maxHandicap is provided but not a finite positive number |
| INVALID_ROUND_TO | roundTo is provided but not a finite positive integer |
| INVALID_HANDICAP_PERCENTAGE | handicapPercentage is provided but not a finite number between 1 and 100 |
| INVALID_ROUNDING | rounding (cricket) is provided but not 'round', 'ceil', or 'floor' |
Types
All TypeScript types are exported for convenience:
import type {
Direction,
AverageType,
PlayerAverage,
PlayerResult,
X01HandicapInput,
X01HandicapResult,
CricketAverageType,
CricketRounding,
CricketHandicapInput,
CricketPlayerResult,
CricketHandicapResult,
HandicapErrorCode,
} from '@tungstenstudio/darts-handicap-calculator';License
ISC
