feat : add accuracy calculation

This commit is contained in:
GuillaumeSD
2024-02-28 23:29:30 +01:00
parent 43017c89ee
commit a8da159870
8 changed files with 283 additions and 677 deletions

View File

@@ -0,0 +1,111 @@
import {
ceilsNumber,
getHarmonicMean,
getStandardDeviation,
getWeightedMean,
} from "@/lib/helpers";
import { Accuracy, MoveEval } from "@/types/eval";
export const computeAccuracy = (moves: MoveEval[]): Accuracy => {
const movesWinPercentage = moves.map(getPositionWinPercentage);
const weights = getAccuracyWeights(movesWinPercentage);
const movesAccuracy = getMovesAccuracy(movesWinPercentage);
const whiteAccuracy = getPlayerAccuracy(movesAccuracy, weights, "white");
const blackAccuracy = getPlayerAccuracy(movesAccuracy, weights, "black");
return {
white: whiteAccuracy,
black: blackAccuracy,
};
};
const getPlayerAccuracy = (
movesAccuracy: number[],
weights: number[],
player: "white" | "black"
): number => {
const remainder = player === "white" ? 0 : 1;
const playerAccuracies = movesAccuracy.filter(
(_, index) => index % 2 === remainder
);
const playerWeights = weights.filter((_, index) => index % 2 === remainder);
const weightedMean = getWeightedMean(playerAccuracies, playerWeights);
const harmonicMean = getHarmonicMean(playerAccuracies);
return (weightedMean + harmonicMean) / 2;
};
const getAccuracyWeights = (movesWinPercentage: number[]): number[] => {
const windowSize = ceilsNumber(
Math.ceil(movesWinPercentage.length / 10),
2,
8
);
const windows: number[][] = [];
const halfWindowSize = Math.round(windowSize / 2);
for (let i = 1; i < movesWinPercentage.length; i++) {
const startIdx = i - halfWindowSize;
const endIdx = i + halfWindowSize;
if (startIdx < 0) {
windows.push(movesWinPercentage.slice(0, windowSize));
continue;
}
if (endIdx > movesWinPercentage.length) {
windows.push(movesWinPercentage.slice(-windowSize));
continue;
}
windows.push(movesWinPercentage.slice(startIdx, endIdx));
}
const weights = windows.map((window) => {
const std = getStandardDeviation(window);
return ceilsNumber(std, 0.5, 12);
});
return weights;
};
const getMovesAccuracy = (movesWinPercentage: number[]): number[] =>
movesWinPercentage.slice(1).map((winPercent, index) => {
const lastWinPercent = movesWinPercentage[index];
const winDiff = Math.abs(lastWinPercent - winPercent);
const rawAccuracy =
103.1668100711649 * Math.exp(-0.04354415386753951 * winDiff) -
3.166924740191411;
return Math.min(100, Math.max(0, rawAccuracy + 1));
});
const getPositionWinPercentage = (move: MoveEval): number => {
if (move.lines[0].cp !== undefined) {
return getWinPercentageFromCp(move.lines[0].cp);
}
if (move.lines[0].mate !== undefined) {
return getWinPercentageFromMate(move.lines[0].mate);
}
throw new Error("No cp or mate in move");
};
const getWinPercentageFromMate = (mate: number): number => {
const mateInf = mate * Infinity;
return getWinPercentageFromCp(mateInf);
};
const getWinPercentageFromCp = (cp: number): number => {
const cpCeiled = ceilsNumber(cp, -1000, 1000);
const MULTIPLIER = -0.00368208;
const winChances = 2 / (1 + Math.exp(MULTIPLIER * cpCeiled)) - 1;
return 50 + 50 * winChances;
};