Last active
May 28, 2025 10:41
-
-
Save tariknz/92453728ed1de844006bfabe07fafcae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Script to calculate the iRating gain/loss for a driver based on their current position | |
**/ | |
// Sample data | |
const drivers: Driver[] = [ | |
{ idx: 0, rating: 2502, position: 1, didNotStart: false }, | |
{ idx: 1, rating: 1202, position: 2, didNotStart: false }, | |
{ idx: 2, rating: 1585, position: 3, didNotStart: false }, | |
{ idx: 3, rating: 1409, position: 4, didNotStart: false }, | |
{ idx: 4, rating: 1531, position: 5, didNotStart: false }, | |
{ idx: 5, rating: 967, position: 6, didNotStart: false }, | |
{ idx: 6, rating: 1016, position: 7, didNotStart: false }, | |
{ idx: 7, rating: 459, position: 8, didNotStart: false }, | |
{ idx: 8, rating: 770, position: 9, didNotStart: false }, | |
{ idx: 9, rating: 926, position: 10, didNotStart: false }, | |
{ idx: 10, rating: 815, position: 11, didNotStart: true }, | |
{ idx: 11, rating: 1518, position: 12, didNotStart: true } | |
]; | |
// Main execution | |
const results = processRaceResults(drivers); | |
console.table(results); | |
/** | |
* Process race results and calculate iRating changes for all drivers | |
*/ | |
function processRaceResults(drivers: Driver[]): Driver[] { | |
const nonStarters = drivers.filter(driver => driver.didNotStart); | |
const results: Driver[] = []; | |
// Process starters | |
for (const driver of drivers) { | |
if (driver.didNotStart) continue; | |
// Calculate expected score against all other drivers | |
const expectedScore = drivers.reduce((sum: number, opponent: Driver) => { | |
return sum + calculateExpectedScore(driver.rating, opponent.rating); | |
}, 0) - 0.5; | |
// Calculate fudge factor | |
const adjustedMidpoint = (drivers.length - (nonStarters.length / 2)) / 2; | |
const fudgeFactor = (adjustedMidpoint - driver.position) / 100; | |
// Calculate iRating change | |
const numerator = (drivers.length - driver.position - expectedScore - fudgeFactor) * 200; | |
const denominator = drivers.length - nonStarters.length; | |
const iRatingChange = numerator / denominator; | |
results.push({ ...driver, iRatingChange }); | |
} | |
// Process non-starters | |
if (nonStarters.length > 0) { | |
const sumOfStarterChanges = results.reduce((sum: number, driver: Driver) => sum + (driver.iRatingChange ?? 0), 0); | |
// Calculate scores for non-starters | |
const nonStarterScores = nonStarters.map(nonStarter => | |
drivers.reduce((sum: number, opponent: Driver) => { | |
return sum + calculateExpectedScore(nonStarter.rating, opponent.rating); | |
}, 0) - 0.5 | |
); | |
const averageNonStarterScore = nonStarterScores.reduce((sum: number, score: number) => sum + score, 0) / nonStarters.length; | |
// Calculate iRating changes for non-starters | |
for (const nonStarter of nonStarters) { | |
const score = drivers.reduce((sum: number, opponent: Driver) => { | |
return sum + calculateExpectedScore(nonStarter.rating, opponent.rating); | |
}, 0) - 0.5; | |
const iRatingChange = -(sumOfStarterChanges / nonStarters.length) * (score / averageNonStarterScore); | |
results.push({ ...nonStarter, iRatingChange }); | |
} | |
} | |
return results; | |
} | |
/** | |
* Calculate the expected score between two drivers using the Elo rating system | |
*/ | |
function calculateExpectedScore(playerRating: number, opponentRating: number): number { | |
const ELO_SCALE = 1600 / Math.log(2); | |
const expPlayer = Math.exp(-playerRating / ELO_SCALE); | |
const expOpponent = Math.exp(-opponentRating / ELO_SCALE); | |
const numerator = (1 - expPlayer) * expOpponent; | |
const denominator = (1 - expOpponent) * expPlayer + (1 - expPlayer) * expOpponent; | |
return numerator / denominator; | |
} | |
interface Driver { | |
idx: number; | |
rating: number; | |
position: number; | |
didNotStart: boolean; | |
iRatingChange?: number; | |
} | |
/** | |
┌─────────┬─────┬────────┬──────────┬─────────────┬─────────────────────┐ | |
│ (index) │ idx │ rating │ position │ didNotStart │ iRatingChange │ | |
├─────────┼─────┼────────┼──────────┼─────────────┼─────────────────────┤ | |
│ 0 │ 0 │ 2502 │ 1 │ false │ 51.526181519664114 │ | |
│ 1 │ 1 │ 1202 │ 2 │ false │ 85.93706251432899 │ | |
│ 2 │ 2 │ 1585 │ 3 │ false │ 45.76839250350699 │ | |
│ 3 │ 3 │ 1409 │ 4 │ false │ 34.726798295045704 │ | |
│ 4 │ 4 │ 1531 │ 5 │ false │ 8.75717685896802 │ | |
│ 5 │ 5 │ 967 │ 6 │ false │ 22.00892280681238 │ | |
│ 6 │ 6 │ 1016 │ 7 │ false │ -1.1847185527524153 │ | |
│ 7 │ 7 │ 459 │ 8 │ false │ 26.417252365382087 │ | |
│ 8 │ 8 │ 770 │ 9 │ false │ -22.43830479379796 │ | |
│ 9 │ 9 │ 926 │ 10 │ false │ -54.257888044345336 │ | |
│ 10 │ 10 │ 815 │ 11 │ true │ -78.76773919166914 │ | |
│ 11 │ 11 │ 1518 │ 12 │ true │ -118.49313628114338 │ | |
└─────────┴─────┴────────┴──────────┴─────────────┴─────────────────────┘ | |
**/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment