From 7e37999d4327bd3e2a431709859e333720a27680 Mon Sep 17 00:00:00 2001 From: Timothy Armes Date: Fri, 15 Sep 2023 13:03:42 +0200 Subject: [PATCH] Fix elo calculations --- app/[locale]/elo/page.tsx | 30 ++++---------- utils/eloCalculator.ts | 86 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 utils/eloCalculator.ts diff --git a/app/[locale]/elo/page.tsx b/app/[locale]/elo/page.tsx index 5c5ff9f..6d9d9e1 100644 --- a/app/[locale]/elo/page.tsx +++ b/app/[locale]/elo/page.tsx @@ -9,26 +9,10 @@ import { z } from "zod"; import { SelectField } from "@/components/form/SelectField"; import { TextField } from "@/components/form/TextField"; +import { getNewRating } from "@/utils/eloCalculator"; import { FetchResultsForm } from "./FetchResultsForm"; -const getNewRating = ( - rating: number, - opponentRating: number, - result: 1 | 0 | 0.5, - kFactor: number, -) => { - const myChanceToWin = 1 / (1 + Math.pow(10, (opponentRating - rating) / 400)); - const delta = - Math.round((kFactor * (result - myChanceToWin) + Number.EPSILON) * 100) / - 100; - - return { - delta, - newRating: Math.round((rating + delta + Number.EPSILON) * 100) / 100, - }; -}; - const resultsSchema = z.object({ currentElo: z.number().int().positive(), kFactor: z.enum(["40", "30", "20", "15", "10"]), @@ -88,16 +72,15 @@ export default function Elo() { const result = game?.result === "win" ? 1 : game?.result === "loss" ? 0 : 0.5; - const { delta, newRating } = getNewRating( - acc.rating, + const { delta } = getNewRating( + currentElo!, game.opponentElo!, result, parseInt(kFactor!, 10), ); - return { - rating: newRating, - deltas: [...acc.deltas, { rating: newRating, delta }], + rating: acc.rating + delta, + deltas: [...acc.deltas, { rating: acc.rating + delta, delta }], }; } @@ -216,7 +199,8 @@ export default function Elo() { deltas[i].delta! < 0 && "text-error", )} > - {deltas[i]!.delta! >= 0 ? "+" : ""} {deltas[i].delta!} + {deltas[i]!.delta! >= 0 ? "+" : ""} + {deltas[i].delta!} ) diff --git a/utils/eloCalculator.ts b/utils/eloCalculator.ts new file mode 100644 index 0000000..1ffac22 --- /dev/null +++ b/utils/eloCalculator.ts @@ -0,0 +1,86 @@ +import { clamp, last } from "lodash"; + +// Fide uses teh following table to establish the chance of winning based on the difference in elo. +// https://handbook.fide.com/chapter/B022017 + +const differenceToProbability: [ + start: number, + end: number, + winChance: number, +][] = [ + [0, 3, 0.5], + [4, 10, 0.51], + [11, 17, 0.52], + [18, 25, 0.53], + [26, 32, 0.54], + [33, 39, 0.55], + [40, 46, 0.56], + [47, 53, 0.57], + [54, 61, 0.58], + [62, 68, 0.59], + [69, 76, 0.6], + [77, 83, 0.61], + [84, 91, 0.62], + [92, 98, 0.63], + [99, 106, 0.64], + [107, 113, 0.65], + [114, 121, 0.66], + [122, 129, 0.67], + [130, 137, 0.68], + [138, 145, 0.69], + [146, 153, 0.7], + [154, 162, 0.71], + [163, 170, 0.72], + [171, 179, 0.73], + [180, 188, 0.74], + [189, 197, 0.75], + [198, 206, 0.76], + [207, 215, 0.77], + [216, 225, 0.78], + [226, 235, 0.79], + [236, 245, 0.8], + [246, 256, 0.81], + [257, 267, 0.82], + [268, 278, 0.83], + [279, 290, 0.84], + [291, 302, 0.85], + [303, 315, 0.86], + [316, 328, 0.87], + [329, 344, 0.88], + [345, 357, 0.89], + [358, 374, 0.9], + [375, 391, 0.91], + [392, 411, 0.92], + [412, 432, 0.93], + [433, 456, 0.94], + [457, 484, 0.95], + [485, 517, 0.96], + [518, 559, 0.97], + [560, 619, 0.98], + [620, 735, 0.99], + [736, 1000, 1], +]; + +export const getNewRating = ( + rating: number, + opponentRating: number, + result: 1 | 0 | 0.5, + kFactor: number, +) => { + const differenceInElo = clamp(opponentRating - rating, -400, 400); + const tableRow = (differenceToProbability.find( + ([start, end]) => + Math.abs(differenceInElo) >= start && Math.abs(differenceInElo) <= end, + ) ?? last(differenceToProbability)!)[2]; + + const probabilityOfWinning = differenceInElo < 0 ? tableRow : 1 - tableRow; + const delta = + Math.round( + kFactor * (result - probabilityOfWinning + Number.EPSILON) * 100, + ) / 100; + + return { + delta, + newRating: Math.round((rating + delta + Number.EPSILON) * 100) / 100, + }; +};