@tungstenstudio/darts-handicap-calculator 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # @tungstenstudio/darts-handicap-calculator
2
+
3
+ Handicap calculator for x01 and cricket darts matches. Uses pure ratio methods — no lookup tables, flat offsets, or tiered brackets.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @tungstenstudio/darts-handicap-calculator
9
+ # or
10
+ pnpm add @tungstenstudio/darts-handicap-calculator
11
+ # or
12
+ yarn add @tungstenstudio/darts-handicap-calculator
13
+ # or
14
+ bun add @tungstenstudio/darts-handicap-calculator
15
+ ```
16
+
17
+ ## X01 Handicap
18
+
19
+ Scores are adjusted proportionally by the ratio of player averages (PPD), so both players need roughly the same number of darts to finish.
20
+
21
+ **Formula:** `adjustedScore = baseScore × (weakerPPD ÷ strongerPPD)`, rounded to the nearest integer.
22
+
23
+ ### Quick Start
24
+
25
+ ```ts
26
+ import { calculateX01Handicap } from '@tungstenstudio/darts-handicap-calculator';
27
+
28
+ const result = calculateX01Handicap({
29
+ baseScore: 501,
30
+ direction: 'negative',
31
+ averageType: 'ppd',
32
+ playerA: { value: 22, name: 'Alice' },
33
+ playerB: { value: 16, name: 'Bob' },
34
+ });
35
+
36
+ result.playerA.startScore; // 501 (stronger player keeps base)
37
+ result.playerB.startScore; // 364 (weaker player starts lower)
38
+ result.handicapPoints; // 137
39
+ result.playerA.estimatedDarts; // 23
40
+ result.playerB.estimatedDarts; // 23
41
+ result.playerA.name; // 'Alice'
42
+ result.capped; // false
43
+ ```
44
+
45
+ #### With a Handicap Cap
46
+
47
+ ```ts
48
+ const result = calculateX01Handicap({
49
+ baseScore: 501,
50
+ direction: 'negative',
51
+ averageType: 'ppd',
52
+ playerA: { value: 20 },
53
+ playerB: { value: 10 },
54
+ maxHandicap: 150,
55
+ });
56
+
57
+ result.playerA.startScore; // 501
58
+ result.playerB.startScore; // 351 (capped — would be 251 without cap)
59
+ result.handicapPoints; // 150
60
+ result.capped; // true
61
+ ```
62
+
63
+ #### With Score Rounding
64
+
65
+ ```ts
66
+ const result = calculateX01Handicap({
67
+ baseScore: 501,
68
+ direction: 'negative',
69
+ averageType: 'ppd',
70
+ playerA: { value: 17 },
71
+ playerB: { value: 12 },
72
+ roundTo: 25,
73
+ });
74
+
75
+ result.playerB.startScore; // 350 (354 rounded to nearest 25)
76
+ result.rounded; // true
77
+ ```
78
+
79
+ #### With Handicap Percentage
80
+
81
+ ```ts
82
+ const result = calculateX01Handicap({
83
+ baseScore: 501,
84
+ direction: 'negative',
85
+ averageType: 'ppd',
86
+ playerA: { value: 20 },
87
+ playerB: { value: 10 },
88
+ handicapPercentage: 75,
89
+ });
90
+
91
+ result.playerB.startScore; // 313 (75% of 250-point gap applied)
92
+ result.handicapPoints; // 188
93
+ result.compressed; // true
94
+ ```
95
+
96
+ ### API
97
+
98
+ #### `calculateX01Handicap(input: X01HandicapInput): X01HandicapResult`
99
+
100
+ Calculates handicapped start scores for two players.
101
+
102
+ ```ts
103
+ interface X01HandicapInput {
104
+ baseScore: number; // The base game score (e.g. 301, 501, 701)
105
+ direction: Direction; // 'negative' | 'positive'
106
+ averageType: AverageType; // 'ppd' | '3da'
107
+ playerA: PlayerAverage;
108
+ playerB: PlayerAverage;
109
+ maxHandicap?: number; // Optional cap on the handicap spread
110
+ roundTo?: number; // Round adjusted score to nearest N (e.g. 5, 10, 25)
111
+ handicapPercentage?: number; // Apply only N% of the handicap spread (1–100, default: 100)
112
+ }
113
+
114
+ interface PlayerAverage {
115
+ value: number; // The player's average
116
+ name?: string; // Optional player name (passed through to result)
117
+ }
118
+ ```
119
+
120
+ Returns:
121
+
122
+ ```ts
123
+ interface X01HandicapResult {
124
+ baseScore: number;
125
+ direction: Direction;
126
+ handicapPoints: number; // Absolute difference between start scores
127
+ capped: boolean; // Whether maxHandicap was applied
128
+ rounded: boolean; // Whether roundTo changed the score
129
+ compressed: boolean; // Whether handicapPercentage reduced the spread
130
+ playerA: PlayerResult;
131
+ playerB: PlayerResult;
132
+ }
133
+
134
+ interface PlayerResult {
135
+ startScore: number; // The handicapped start score
136
+ ppd: number; // Points per dart
137
+ threeDartAverage: number; // 3-dart average
138
+ estimatedDarts: number; // Estimated darts to finish (ceil(startScore / ppd))
139
+ name?: string; // Player name (if provided in input)
140
+ }
141
+ ```
142
+
143
+ #### Direction
144
+
145
+ The `direction` parameter controls which player's score is adjusted:
146
+
147
+ | Direction | Stronger player | Weaker player |
148
+ |-----------|----------------|---------------|
149
+ | `'negative'` | Keeps the base score | Starts **lower** (reduced proportionally) |
150
+ | `'positive'` | Starts **higher** (raised proportionally) | Keeps the base score |
151
+
152
+ Both directions produce the same *relative* handicap. Choose based on your league's convention:
153
+ - **Negative** is the most common — it keeps the strong player at the standard game score.
154
+ - **Positive** is useful when you want all players to start at *at least* the base score.
155
+
156
+ ## Cricket Handicap
157
+
158
+ 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.
159
+
160
+ **Formula:** `spotMarks = 21 × (1 − weakerAvg ÷ strongerAvg)`, rounded to the nearest integer. The total of 21 represents 3 marks × 7 cricket numbers.
161
+
162
+ ### Quick Start
163
+
164
+ ```ts
165
+ import { calculateCricketHandicap } from '@tungstenstudio/darts-handicap-calculator';
166
+
167
+ const result = calculateCricketHandicap({
168
+ averageType: 'mpr',
169
+ playerA: { value: 3.0, name: 'Alice' },
170
+ playerB: { value: 2.0, name: 'Bob' },
171
+ });
172
+
173
+ result.spotMarks; // 7
174
+ result.playerA.startingMarks; // 0 (stronger player starts at 0)
175
+ result.playerB.startingMarks; // 7 (weaker player gets spot marks)
176
+ result.playerA.estimatedRounds; // 7
177
+ result.playerB.estimatedRounds; // 7
178
+ ```
179
+
180
+ #### With a Handicap Cap
181
+
182
+ ```ts
183
+ const result = calculateCricketHandicap({
184
+ averageType: 'mpr',
185
+ playerA: { value: 4.0 },
186
+ playerB: { value: 1.0 },
187
+ maxHandicap: 10,
188
+ });
189
+
190
+ result.spotMarks; // 10 (capped — would be 16 without cap)
191
+ result.capped; // true
192
+ ```
193
+
194
+ #### With Rounding Mode
195
+
196
+ ```ts
197
+ const result = calculateCricketHandicap({
198
+ averageType: 'mpr',
199
+ playerA: { value: 2.5 },
200
+ playerB: { value: 2.0 },
201
+ rounding: 'ceil', // 'round' (default) | 'ceil' | 'floor'
202
+ });
203
+
204
+ result.spotMarks; // 5 (4.2 ceiled — would be 4 with default rounding)
205
+ ```
206
+
207
+ #### With Handicap Percentage
208
+
209
+ ```ts
210
+ const result = calculateCricketHandicap({
211
+ averageType: 'mpr',
212
+ playerA: { value: 3.0 },
213
+ playerB: { value: 2.0 },
214
+ handicapPercentage: 50,
215
+ });
216
+
217
+ result.spotMarks; // 4 (50% of 7 spot marks)
218
+ result.compressed; // true
219
+ ```
220
+
221
+ ### API
222
+
223
+ #### `calculateCricketHandicap(input: CricketHandicapInput): CricketHandicapResult`
224
+
225
+ Calculates spot marks for two cricket players.
226
+
227
+ ```ts
228
+ interface CricketHandicapInput {
229
+ averageType: CricketAverageType; // 'mpr' | 'ppd'
230
+ playerA: PlayerAverage;
231
+ playerB: PlayerAverage;
232
+ maxHandicap?: number; // Optional cap on spot marks
233
+ rounding?: CricketRounding; // 'round' (default) | 'ceil' | 'floor'
234
+ handicapPercentage?: number; // Apply only N% of the spot marks (1–100, default: 100)
235
+ }
236
+ ```
237
+
238
+ Returns:
239
+
240
+ ```ts
241
+ interface CricketHandicapResult {
242
+ spotMarks: number; // Marks spotted to the weaker player
243
+ capped: boolean; // Whether maxHandicap was applied
244
+ rounding: CricketRounding; // The rounding mode used
245
+ compressed: boolean; // Whether handicapPercentage reduced the spread
246
+ playerA: CricketPlayerResult;
247
+ playerB: CricketPlayerResult;
248
+ }
249
+
250
+ interface CricketPlayerResult {
251
+ startingMarks: number; // 0 for stronger player, spotMarks for weaker
252
+ marksNeeded: number; // 21 − startingMarks
253
+ average: number; // The player's average
254
+ averageType: CricketAverageType; // 'mpr' | 'ppd'
255
+ estimatedRounds: number; // ceil(marksNeeded / average)
256
+ name?: string; // Player name (if provided in input)
257
+ }
258
+ ```
259
+
260
+ ## Utility Functions
261
+
262
+ #### `ppdTo3DA(ppd: number): number`
263
+
264
+ Converts points-per-dart to 3-dart average.
265
+
266
+ ```ts
267
+ ppdTo3DA(20); // 60
268
+ ```
269
+
270
+ #### `threeDAToPPD(tda: number): number`
271
+
272
+ Converts 3-dart average to points-per-dart.
273
+
274
+ ```ts
275
+ threeDAToPPD(60); // 20
276
+ ```
277
+
278
+ ## Error Handling
279
+
280
+ The calculator throws `HandicapError` (extends `Error`) with a `code` property for invalid inputs:
281
+
282
+ ```ts
283
+ import { calculateX01Handicap, HandicapError } from '@tungstenstudio/darts-handicap-calculator';
284
+
285
+ try {
286
+ calculateX01Handicap(input);
287
+ } catch (err) {
288
+ if (err instanceof HandicapError) {
289
+ console.error(err.code); // e.g. 'INVALID_PLAYER_A_AVERAGE'
290
+ console.error(err.message); // Human-readable description
291
+ }
292
+ }
293
+ ```
294
+
295
+ | Error Code | Cause |
296
+ |------------|-------|
297
+ | `INVALID_BASE_SCORE` | `baseScore` is not a finite positive number |
298
+ | `INVALID_PLAYER_A_AVERAGE` | Player A's average is not a finite positive number |
299
+ | `INVALID_PLAYER_B_AVERAGE` | Player B's average is not a finite positive number |
300
+ | `INVALID_MAX_HANDICAP` | `maxHandicap` is provided but not a finite positive number |
301
+ | `INVALID_ROUND_TO` | `roundTo` is provided but not a finite positive integer |
302
+ | `INVALID_HANDICAP_PERCENTAGE` | `handicapPercentage` is provided but not a finite number between 1 and 100 |
303
+ | `INVALID_ROUNDING` | `rounding` (cricket) is provided but not `'round'`, `'ceil'`, or `'floor'` |
304
+
305
+ ## Types
306
+
307
+ All TypeScript types are exported for convenience:
308
+
309
+ ```ts
310
+ import type {
311
+ Direction,
312
+ AverageType,
313
+ PlayerAverage,
314
+ PlayerResult,
315
+ X01HandicapInput,
316
+ X01HandicapResult,
317
+ CricketAverageType,
318
+ CricketRounding,
319
+ CricketHandicapInput,
320
+ CricketPlayerResult,
321
+ CricketHandicapResult,
322
+ HandicapErrorCode,
323
+ } from '@tungstenstudio/darts-handicap-calculator';
324
+ ```
325
+
326
+ ## License
327
+
328
+ ISC
package/dist/index.cjs ADDED
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ HandicapError: () => HandicapError,
24
+ calculateCricketHandicap: () => calculateCricketHandicap,
25
+ calculateX01Handicap: () => calculateX01Handicap,
26
+ ppdTo3DA: () => ppdTo3DA,
27
+ threeDAToPPD: () => threeDAToPPD
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/types.ts
32
+ var HandicapError = class extends Error {
33
+ constructor(message, code) {
34
+ super(message);
35
+ this.code = code;
36
+ this.name = "HandicapError";
37
+ }
38
+ };
39
+
40
+ // src/conversions.ts
41
+ function ppdTo3DA(ppd) {
42
+ return ppd * 3;
43
+ }
44
+ function threeDAToPPD(tda) {
45
+ return tda / 3;
46
+ }
47
+ function toPPD(value, type) {
48
+ if (type === "3da") return threeDAToPPD(value);
49
+ if (type === "ppd") return value;
50
+ throw new Error(`Unknown average type: ${type}`);
51
+ }
52
+
53
+ // src/x01.ts
54
+ function calculateX01Handicap(input) {
55
+ const { baseScore, direction, averageType, playerA, playerB, maxHandicap, roundTo, handicapPercentage } = input;
56
+ if (!Number.isFinite(baseScore) || baseScore <= 0) {
57
+ throw new HandicapError("baseScore must be a finite positive number", "INVALID_BASE_SCORE");
58
+ }
59
+ const ppdA = toPPD(playerA.value, averageType);
60
+ if (!Number.isFinite(ppdA) || ppdA <= 0) {
61
+ throw new HandicapError("Player A average must be a finite positive number", "INVALID_PLAYER_A_AVERAGE");
62
+ }
63
+ const ppdB = toPPD(playerB.value, averageType);
64
+ if (!Number.isFinite(ppdB) || ppdB <= 0) {
65
+ throw new HandicapError("Player B average must be a finite positive number", "INVALID_PLAYER_B_AVERAGE");
66
+ }
67
+ if (maxHandicap !== void 0) {
68
+ if (!Number.isFinite(maxHandicap) || maxHandicap <= 0) {
69
+ throw new HandicapError("maxHandicap must be a finite positive number", "INVALID_MAX_HANDICAP");
70
+ }
71
+ }
72
+ if (roundTo !== void 0) {
73
+ if (!Number.isFinite(roundTo) || roundTo <= 0 || !Number.isInteger(roundTo)) {
74
+ throw new HandicapError("roundTo must be a finite positive integer", "INVALID_ROUND_TO");
75
+ }
76
+ }
77
+ if (handicapPercentage !== void 0) {
78
+ if (!Number.isFinite(handicapPercentage) || handicapPercentage < 1 || handicapPercentage > 100) {
79
+ throw new HandicapError("handicapPercentage must be a finite number between 1 and 100", "INVALID_HANDICAP_PERCENTAGE");
80
+ }
81
+ }
82
+ const strongerPPD = Math.max(ppdA, ppdB);
83
+ const weakerPPD = Math.min(ppdA, ppdB);
84
+ let strongerStart;
85
+ let weakerStart;
86
+ if (direction === "negative") {
87
+ strongerStart = baseScore;
88
+ weakerStart = Math.round(baseScore * (weakerPPD / strongerPPD));
89
+ } else {
90
+ weakerStart = baseScore;
91
+ strongerStart = Math.round(baseScore * (strongerPPD / weakerPPD));
92
+ }
93
+ let compressed = false;
94
+ if (handicapPercentage !== void 0 && handicapPercentage < 100) {
95
+ const rawDiff = Math.abs(strongerStart - weakerStart);
96
+ const adjustedDiff = Math.round(rawDiff * (handicapPercentage / 100));
97
+ if (adjustedDiff !== rawDiff) {
98
+ compressed = true;
99
+ if (direction === "negative") {
100
+ weakerStart = strongerStart - adjustedDiff;
101
+ } else {
102
+ strongerStart = weakerStart + adjustedDiff;
103
+ }
104
+ }
105
+ }
106
+ let capped = false;
107
+ const rawHandicap = Math.abs(strongerStart - weakerStart);
108
+ if (maxHandicap !== void 0 && rawHandicap > maxHandicap) {
109
+ capped = true;
110
+ if (direction === "negative") {
111
+ weakerStart = strongerStart - maxHandicap;
112
+ } else {
113
+ strongerStart = weakerStart + maxHandicap;
114
+ }
115
+ }
116
+ let rounded = false;
117
+ if (roundTo !== void 0 && roundTo > 1) {
118
+ if (direction === "negative") {
119
+ const before = weakerStart;
120
+ weakerStart = Math.max(roundTo, Math.round(weakerStart / roundTo) * roundTo);
121
+ rounded = weakerStart !== before;
122
+ } else {
123
+ const before = strongerStart;
124
+ strongerStart = Math.max(roundTo, Math.round(strongerStart / roundTo) * roundTo);
125
+ rounded = strongerStart !== before;
126
+ }
127
+ }
128
+ const aIsStronger = ppdA >= ppdB;
129
+ function buildResult(ppd, startScore, avg) {
130
+ const result = {
131
+ startScore,
132
+ ppd,
133
+ threeDartAverage: ppdTo3DA(ppd),
134
+ estimatedDarts: Math.ceil(startScore / ppd)
135
+ };
136
+ if (avg.name !== void 0) {
137
+ result.name = avg.name;
138
+ }
139
+ return result;
140
+ }
141
+ return {
142
+ baseScore,
143
+ direction,
144
+ handicapPoints: Math.abs(strongerStart - weakerStart),
145
+ capped,
146
+ rounded,
147
+ compressed,
148
+ playerA: buildResult(ppdA, aIsStronger ? strongerStart : weakerStart, playerA),
149
+ playerB: buildResult(ppdB, aIsStronger ? weakerStart : strongerStart, playerB)
150
+ };
151
+ }
152
+
153
+ // src/cricket.ts
154
+ var TOTAL_MARKS = 21;
155
+ var VALID_ROUNDING = ["round", "ceil", "floor"];
156
+ function roundFn(mode) {
157
+ if (mode === "ceil") return Math.ceil;
158
+ if (mode === "floor") return Math.floor;
159
+ return Math.round;
160
+ }
161
+ function calculateCricketHandicap(input) {
162
+ const { averageType, playerA, playerB, maxHandicap, handicapPercentage } = input;
163
+ const rounding = input.rounding ?? "round";
164
+ const avgA = playerA.value;
165
+ if (!Number.isFinite(avgA) || avgA <= 0) {
166
+ throw new HandicapError("Player A average must be a finite positive number", "INVALID_PLAYER_A_AVERAGE");
167
+ }
168
+ const avgB = playerB.value;
169
+ if (!Number.isFinite(avgB) || avgB <= 0) {
170
+ throw new HandicapError("Player B average must be a finite positive number", "INVALID_PLAYER_B_AVERAGE");
171
+ }
172
+ if (maxHandicap !== void 0) {
173
+ if (!Number.isFinite(maxHandicap) || maxHandicap <= 0) {
174
+ throw new HandicapError("maxHandicap must be a finite positive number", "INVALID_MAX_HANDICAP");
175
+ }
176
+ }
177
+ if (input.rounding !== void 0) {
178
+ if (!VALID_ROUNDING.includes(input.rounding)) {
179
+ throw new HandicapError(
180
+ "rounding must be 'round', 'ceil', or 'floor'",
181
+ "INVALID_ROUNDING"
182
+ );
183
+ }
184
+ }
185
+ if (handicapPercentage !== void 0) {
186
+ if (!Number.isFinite(handicapPercentage) || handicapPercentage < 1 || handicapPercentage > 100) {
187
+ throw new HandicapError("handicapPercentage must be a finite number between 1 and 100", "INVALID_HANDICAP_PERCENTAGE");
188
+ }
189
+ }
190
+ const strongerAvg = Math.max(avgA, avgB);
191
+ const weakerAvg = Math.min(avgA, avgB);
192
+ const rawSpotMarks = TOTAL_MARKS * (1 - weakerAvg / strongerAvg);
193
+ const round = roundFn(rounding);
194
+ let spotMarks = round(rawSpotMarks);
195
+ let compressed = false;
196
+ if (handicapPercentage !== void 0 && handicapPercentage < 100) {
197
+ const adjustedMarks = round(spotMarks * (handicapPercentage / 100));
198
+ if (adjustedMarks !== spotMarks) {
199
+ compressed = true;
200
+ spotMarks = adjustedMarks;
201
+ }
202
+ }
203
+ let capped = false;
204
+ if (maxHandicap !== void 0 && spotMarks > maxHandicap) {
205
+ capped = true;
206
+ spotMarks = maxHandicap;
207
+ }
208
+ const aIsStronger = avgA >= avgB;
209
+ function buildResult(avg, isStronger) {
210
+ const startingMarks = isStronger ? 0 : spotMarks;
211
+ const marksNeeded = TOTAL_MARKS - startingMarks;
212
+ const result = {
213
+ startingMarks,
214
+ marksNeeded,
215
+ average: avg.value,
216
+ averageType,
217
+ estimatedRounds: Math.ceil(marksNeeded / avg.value)
218
+ };
219
+ if (avg.name !== void 0) {
220
+ result.name = avg.name;
221
+ }
222
+ return result;
223
+ }
224
+ return {
225
+ spotMarks,
226
+ capped,
227
+ rounding,
228
+ compressed,
229
+ playerA: buildResult(playerA, aIsStronger),
230
+ playerB: buildResult(playerB, !aIsStronger)
231
+ };
232
+ }
233
+ // Annotate the CommonJS export names for ESM import in node:
234
+ 0 && (module.exports = {
235
+ HandicapError,
236
+ calculateCricketHandicap,
237
+ calculateX01Handicap,
238
+ ppdTo3DA,
239
+ threeDAToPPD
240
+ });
241
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/conversions.ts","../src/x01.ts","../src/cricket.ts"],"sourcesContent":["export type {\n Direction,\n AverageType,\n PlayerAverage,\n PlayerResult,\n X01HandicapInput,\n X01HandicapResult,\n HandicapErrorCode,\n CricketAverageType,\n CricketRounding,\n CricketHandicapInput,\n CricketPlayerResult,\n CricketHandicapResult,\n} from './types.js';\n\nexport { HandicapError } from './types.js';\nexport { ppdTo3DA, threeDAToPPD } from './conversions.js';\nexport { calculateX01Handicap } from './x01.js';\nexport { calculateCricketHandicap } from './cricket.js';\n","/**\n * Controls which player's start score is adjusted.\n *\n * - `'negative'` — The stronger player keeps the base score; the weaker\n * player's start score is reduced proportionally.\n * - `'positive'` — The weaker player keeps the base score; the stronger\n * player's start score is raised proportionally.\n */\nexport type Direction = 'positive' | 'negative';\n\nexport type AverageType = 'ppd' | '3da';\n\nexport interface PlayerAverage {\n value: number;\n name?: string;\n}\n\nexport interface PlayerResult {\n startScore: number;\n ppd: number;\n threeDartAverage: number;\n estimatedDarts: number;\n name?: string;\n}\n\nexport interface X01HandicapInput {\n baseScore: number;\n direction: Direction;\n averageType: AverageType;\n playerA: PlayerAverage;\n playerB: PlayerAverage;\n maxHandicap?: number;\n roundTo?: number;\n handicapPercentage?: number;\n}\n\nexport interface X01HandicapResult {\n baseScore: number;\n direction: Direction;\n /** Absolute difference between the two start scores. */\n handicapPoints: number;\n /** Whether the handicap was capped by maxHandicap. */\n capped: boolean;\n /** Whether the start score was rounded by roundTo. */\n rounded: boolean;\n /** Whether handicapPercentage compressed the spread. */\n compressed: boolean;\n playerA: PlayerResult;\n playerB: PlayerResult;\n}\n\nexport type HandicapErrorCode =\n | 'INVALID_BASE_SCORE'\n | 'INVALID_PLAYER_A_AVERAGE'\n | 'INVALID_PLAYER_B_AVERAGE'\n | 'INVALID_MAX_HANDICAP'\n | 'INVALID_ROUND_TO'\n | 'INVALID_ROUNDING'\n | 'INVALID_HANDICAP_PERCENTAGE';\n\nexport class HandicapError extends Error {\n constructor(\n message: string,\n public readonly code: HandicapErrorCode,\n ) {\n super(message);\n this.name = 'HandicapError';\n }\n}\n\nexport type CricketAverageType = 'mpr' | 'ppd';\n\nexport type CricketRounding = 'round' | 'ceil' | 'floor';\n\nexport interface CricketHandicapInput {\n averageType: CricketAverageType;\n playerA: PlayerAverage;\n playerB: PlayerAverage;\n maxHandicap?: number;\n rounding?: CricketRounding;\n handicapPercentage?: number;\n}\n\nexport interface CricketPlayerResult {\n startingMarks: number;\n marksNeeded: number;\n average: number;\n averageType: CricketAverageType;\n estimatedRounds: number;\n name?: string;\n}\n\nexport interface CricketHandicapResult {\n /** Number of marks spotted to the weaker player. */\n spotMarks: number;\n /** Whether the handicap was capped by maxHandicap. */\n capped: boolean;\n /** The rounding mode used ('round' | 'ceil' | 'floor'). */\n rounding: CricketRounding;\n /** Whether handicapPercentage compressed the spread. */\n compressed: boolean;\n playerA: CricketPlayerResult;\n playerB: CricketPlayerResult;\n}\n","import type { AverageType } from './types.js';\n\nexport function ppdTo3DA(ppd: number): number {\n return ppd * 3;\n}\n\nexport function threeDAToPPD(tda: number): number {\n return tda / 3;\n}\n\nexport function toPPD(value: number, type: AverageType): number {\n if (type === '3da') return threeDAToPPD(value);\n if (type === 'ppd') return value;\n throw new Error(`Unknown average type: ${type as string}`);\n}\n","import type { X01HandicapInput, X01HandicapResult, PlayerResult, PlayerAverage } from './types.js';\nimport { HandicapError } from './types.js';\nimport { toPPD, ppdTo3DA } from './conversions.js';\n\nexport function calculateX01Handicap(input: X01HandicapInput): X01HandicapResult {\n const { baseScore, direction, averageType, playerA, playerB, maxHandicap, roundTo, handicapPercentage } = input;\n\n if (!Number.isFinite(baseScore) || baseScore <= 0) {\n throw new HandicapError('baseScore must be a finite positive number', 'INVALID_BASE_SCORE');\n }\n\n const ppdA = toPPD(playerA.value, averageType);\n if (!Number.isFinite(ppdA) || ppdA <= 0) {\n throw new HandicapError('Player A average must be a finite positive number', 'INVALID_PLAYER_A_AVERAGE');\n }\n\n const ppdB = toPPD(playerB.value, averageType);\n if (!Number.isFinite(ppdB) || ppdB <= 0) {\n throw new HandicapError('Player B average must be a finite positive number', 'INVALID_PLAYER_B_AVERAGE');\n }\n\n if (maxHandicap !== undefined) {\n if (!Number.isFinite(maxHandicap) || maxHandicap <= 0) {\n throw new HandicapError('maxHandicap must be a finite positive number', 'INVALID_MAX_HANDICAP');\n }\n }\n\n if (roundTo !== undefined) {\n if (!Number.isFinite(roundTo) || roundTo <= 0 || !Number.isInteger(roundTo)) {\n throw new HandicapError('roundTo must be a finite positive integer', 'INVALID_ROUND_TO');\n }\n }\n\n if (handicapPercentage !== undefined) {\n if (!Number.isFinite(handicapPercentage) || handicapPercentage < 1 || handicapPercentage > 100) {\n throw new HandicapError('handicapPercentage must be a finite number between 1 and 100', 'INVALID_HANDICAP_PERCENTAGE');\n }\n }\n\n const strongerPPD = Math.max(ppdA, ppdB);\n const weakerPPD = Math.min(ppdA, ppdB);\n\n let strongerStart: number;\n let weakerStart: number;\n\n if (direction === 'negative') {\n strongerStart = baseScore;\n weakerStart = Math.round(baseScore * (weakerPPD / strongerPPD));\n } else {\n weakerStart = baseScore;\n strongerStart = Math.round(baseScore * (strongerPPD / weakerPPD));\n }\n\n let compressed = false;\n if (handicapPercentage !== undefined && handicapPercentage < 100) {\n const rawDiff = Math.abs(strongerStart - weakerStart);\n const adjustedDiff = Math.round(rawDiff * (handicapPercentage / 100));\n if (adjustedDiff !== rawDiff) {\n compressed = true;\n if (direction === 'negative') {\n weakerStart = strongerStart - adjustedDiff;\n } else {\n strongerStart = weakerStart + adjustedDiff;\n }\n }\n }\n\n let capped = false;\n const rawHandicap = Math.abs(strongerStart - weakerStart);\n\n if (maxHandicap !== undefined && rawHandicap > maxHandicap) {\n capped = true;\n if (direction === 'negative') {\n weakerStart = strongerStart - maxHandicap;\n } else {\n strongerStart = weakerStart + maxHandicap;\n }\n }\n\n let rounded = false;\n if (roundTo !== undefined && roundTo > 1) {\n if (direction === 'negative') {\n const before = weakerStart;\n weakerStart = Math.max(roundTo, Math.round(weakerStart / roundTo) * roundTo);\n rounded = weakerStart !== before;\n } else {\n const before = strongerStart;\n strongerStart = Math.max(roundTo, Math.round(strongerStart / roundTo) * roundTo);\n rounded = strongerStart !== before;\n }\n }\n\n const aIsStronger = ppdA >= ppdB;\n\n function buildResult(ppd: number, startScore: number, avg: PlayerAverage): PlayerResult {\n const result: PlayerResult = {\n startScore,\n ppd,\n threeDartAverage: ppdTo3DA(ppd),\n estimatedDarts: Math.ceil(startScore / ppd),\n };\n if (avg.name !== undefined) {\n result.name = avg.name;\n }\n return result;\n }\n\n return {\n baseScore,\n direction,\n handicapPoints: Math.abs(strongerStart - weakerStart),\n capped,\n rounded,\n compressed,\n playerA: buildResult(ppdA, aIsStronger ? strongerStart : weakerStart, playerA),\n playerB: buildResult(ppdB, aIsStronger ? weakerStart : strongerStart, playerB),\n };\n}\n","import type {\n CricketHandicapInput,\n CricketHandicapResult,\n CricketPlayerResult,\n CricketAverageType,\n CricketRounding,\n PlayerAverage,\n} from './types.js';\nimport { HandicapError } from './types.js';\n\nconst TOTAL_MARKS = 21;\nconst VALID_ROUNDING: CricketRounding[] = ['round', 'ceil', 'floor'];\n\nfunction roundFn(mode: CricketRounding): (n: number) => number {\n if (mode === 'ceil') return Math.ceil;\n if (mode === 'floor') return Math.floor;\n return Math.round;\n}\n\nexport function calculateCricketHandicap(input: CricketHandicapInput): CricketHandicapResult {\n const { averageType, playerA, playerB, maxHandicap, handicapPercentage } = input;\n const rounding: CricketRounding = input.rounding ?? 'round';\n\n const avgA = playerA.value;\n if (!Number.isFinite(avgA) || avgA <= 0) {\n throw new HandicapError('Player A average must be a finite positive number', 'INVALID_PLAYER_A_AVERAGE');\n }\n\n const avgB = playerB.value;\n if (!Number.isFinite(avgB) || avgB <= 0) {\n throw new HandicapError('Player B average must be a finite positive number', 'INVALID_PLAYER_B_AVERAGE');\n }\n\n if (maxHandicap !== undefined) {\n if (!Number.isFinite(maxHandicap) || maxHandicap <= 0) {\n throw new HandicapError('maxHandicap must be a finite positive number', 'INVALID_MAX_HANDICAP');\n }\n }\n\n if (input.rounding !== undefined) {\n if (!VALID_ROUNDING.includes(input.rounding)) {\n throw new HandicapError(\n \"rounding must be 'round', 'ceil', or 'floor'\",\n 'INVALID_ROUNDING',\n );\n }\n }\n\n if (handicapPercentage !== undefined) {\n if (!Number.isFinite(handicapPercentage) || handicapPercentage < 1 || handicapPercentage > 100) {\n throw new HandicapError('handicapPercentage must be a finite number between 1 and 100', 'INVALID_HANDICAP_PERCENTAGE');\n }\n }\n\n const strongerAvg = Math.max(avgA, avgB);\n const weakerAvg = Math.min(avgA, avgB);\n\n // Raw spot marks: 21 × (1 - weaker/stronger)\n const rawSpotMarks = TOTAL_MARKS * (1 - weakerAvg / strongerAvg);\n\n // Apply rounding mode\n const round = roundFn(rounding);\n let spotMarks = round(rawSpotMarks);\n\n // Apply handicapPercentage compression\n let compressed = false;\n if (handicapPercentage !== undefined && handicapPercentage < 100) {\n const adjustedMarks = round(spotMarks * (handicapPercentage / 100));\n if (adjustedMarks !== spotMarks) {\n compressed = true;\n spotMarks = adjustedMarks;\n }\n }\n\n // Apply maxHandicap cap\n let capped = false;\n if (maxHandicap !== undefined && spotMarks > maxHandicap) {\n capped = true;\n spotMarks = maxHandicap;\n }\n\n const aIsStronger = avgA >= avgB;\n\n function buildResult(avg: PlayerAverage, isStronger: boolean): CricketPlayerResult {\n const startingMarks = isStronger ? 0 : spotMarks;\n const marksNeeded = TOTAL_MARKS - startingMarks;\n const result: CricketPlayerResult = {\n startingMarks,\n marksNeeded,\n average: avg.value,\n averageType,\n estimatedRounds: Math.ceil(marksNeeded / avg.value),\n };\n if (avg.name !== undefined) {\n result.name = avg.name;\n }\n return result;\n }\n\n return {\n spotMarks,\n capped,\n rounding,\n compressed,\n playerA: buildResult(playerA, aIsStronger),\n playerB: buildResult(playerB, !aIsStronger),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Controls which player's start score is adjusted.
3
+ *
4
+ * - `'negative'` — The stronger player keeps the base score; the weaker
5
+ * player's start score is reduced proportionally.
6
+ * - `'positive'` — The weaker player keeps the base score; the stronger
7
+ * player's start score is raised proportionally.
8
+ */
9
+ type Direction = 'positive' | 'negative';
10
+ type AverageType = 'ppd' | '3da';
11
+ interface PlayerAverage {
12
+ value: number;
13
+ name?: string;
14
+ }
15
+ interface PlayerResult {
16
+ startScore: number;
17
+ ppd: number;
18
+ threeDartAverage: number;
19
+ estimatedDarts: number;
20
+ name?: string;
21
+ }
22
+ interface X01HandicapInput {
23
+ baseScore: number;
24
+ direction: Direction;
25
+ averageType: AverageType;
26
+ playerA: PlayerAverage;
27
+ playerB: PlayerAverage;
28
+ maxHandicap?: number;
29
+ roundTo?: number;
30
+ handicapPercentage?: number;
31
+ }
32
+ interface X01HandicapResult {
33
+ baseScore: number;
34
+ direction: Direction;
35
+ /** Absolute difference between the two start scores. */
36
+ handicapPoints: number;
37
+ /** Whether the handicap was capped by maxHandicap. */
38
+ capped: boolean;
39
+ /** Whether the start score was rounded by roundTo. */
40
+ rounded: boolean;
41
+ /** Whether handicapPercentage compressed the spread. */
42
+ compressed: boolean;
43
+ playerA: PlayerResult;
44
+ playerB: PlayerResult;
45
+ }
46
+ type HandicapErrorCode = 'INVALID_BASE_SCORE' | 'INVALID_PLAYER_A_AVERAGE' | 'INVALID_PLAYER_B_AVERAGE' | 'INVALID_MAX_HANDICAP' | 'INVALID_ROUND_TO' | 'INVALID_ROUNDING' | 'INVALID_HANDICAP_PERCENTAGE';
47
+ declare class HandicapError extends Error {
48
+ readonly code: HandicapErrorCode;
49
+ constructor(message: string, code: HandicapErrorCode);
50
+ }
51
+ type CricketAverageType = 'mpr' | 'ppd';
52
+ type CricketRounding = 'round' | 'ceil' | 'floor';
53
+ interface CricketHandicapInput {
54
+ averageType: CricketAverageType;
55
+ playerA: PlayerAverage;
56
+ playerB: PlayerAverage;
57
+ maxHandicap?: number;
58
+ rounding?: CricketRounding;
59
+ handicapPercentage?: number;
60
+ }
61
+ interface CricketPlayerResult {
62
+ startingMarks: number;
63
+ marksNeeded: number;
64
+ average: number;
65
+ averageType: CricketAverageType;
66
+ estimatedRounds: number;
67
+ name?: string;
68
+ }
69
+ interface CricketHandicapResult {
70
+ /** Number of marks spotted to the weaker player. */
71
+ spotMarks: number;
72
+ /** Whether the handicap was capped by maxHandicap. */
73
+ capped: boolean;
74
+ /** The rounding mode used ('round' | 'ceil' | 'floor'). */
75
+ rounding: CricketRounding;
76
+ /** Whether handicapPercentage compressed the spread. */
77
+ compressed: boolean;
78
+ playerA: CricketPlayerResult;
79
+ playerB: CricketPlayerResult;
80
+ }
81
+
82
+ declare function ppdTo3DA(ppd: number): number;
83
+ declare function threeDAToPPD(tda: number): number;
84
+
85
+ declare function calculateX01Handicap(input: X01HandicapInput): X01HandicapResult;
86
+
87
+ declare function calculateCricketHandicap(input: CricketHandicapInput): CricketHandicapResult;
88
+
89
+ export { type AverageType, type CricketAverageType, type CricketHandicapInput, type CricketHandicapResult, type CricketPlayerResult, type CricketRounding, type Direction, HandicapError, type HandicapErrorCode, type PlayerAverage, type PlayerResult, type X01HandicapInput, type X01HandicapResult, calculateCricketHandicap, calculateX01Handicap, ppdTo3DA, threeDAToPPD };