Skip to content

Instantly share code, notes, and snippets.

@tariknz
Last active May 28, 2025 10:41
Show Gist options
  • Save tariknz/92453728ed1de844006bfabe07fafcae to your computer and use it in GitHub Desktop.
Save tariknz/92453728ed1de844006bfabe07fafcae to your computer and use it in GitHub Desktop.
/**
* 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