feat : add elo estimation feature (#6)

* feat:  add elo estimation feature
This commit is contained in:
Theprathamshah
2025-05-10 01:00:07 +05:30
committed by GitHub
parent 34fdde282c
commit 2cc8b48b08
5 changed files with 78 additions and 6 deletions

View File

@@ -0,0 +1,54 @@
import { ceilsNumber } from "@/lib/math";
import { EstimatedElo, PositionEval } from "@/types/eval";
export const estimateEloFromEngineOutput = (
positions: PositionEval[]
): EstimatedElo => {
try {
if (!positions || positions.length === 0) {
return { white: null, black: null };
}
let totalCPLWhite = 0;
let totalCPLBlack = 0;
let moveCount = 0;
let previousCp = null;
let flag = true;
for (const moveAnalysis of positions) {
if (moveAnalysis.lines && moveAnalysis.lines.length > 0) {
const bestLine = moveAnalysis.lines[0];
if (bestLine.cp !== undefined) {
if (previousCp !== null) {
const diff = Math.abs(bestLine.cp - previousCp);
if (flag) {
totalCPLWhite += ceilsNumber(diff, -1000, 1000);
} else {
totalCPLBlack += ceilsNumber(diff, -1000, 1000);
}
flag = !flag;
moveCount++;
}
previousCp = bestLine.cp;
}
}
}
if (moveCount === 0) {
return { white: null, black: null };
}
const averageCPLWhite = totalCPLWhite / Math.ceil(moveCount / 2);
const averageCPLBlack = totalCPLBlack / Math.floor(moveCount / 2);
const estimateElo = (averageCPL: number) =>
3100 * Math.exp(-0.01 * averageCPL);
const whiteElo = estimateElo(Math.abs(averageCPLWhite));
const blackElo = estimateElo(Math.abs(averageCPLBlack));
return { white: whiteElo, black: blackElo };
} catch (error) {
console.error("Error estimating Elo: ", error);
return { white: null, black: null };
}
};

View File

@@ -1,5 +1,6 @@
import { EngineName } from "@/types/enums";
import {
EstimatedElo,
EvaluateGameParams,
EvaluatePositionWithUpdateParams,
GameEval,
@@ -13,6 +14,7 @@ import { computeAccuracy } from "./helpers/accuracy";
import { getIsStalemate, getWhoIsCheckmated } from "../chess";
import { getLichessEval } from "../lichess";
import { getMovesClassification } from "./helpers/moveClassification";
import { estimateEloFromEngineOutput } from "./helpers/estimateElo";
import { EngineWorker, WorkerJob } from "@/types/engine";
export class UciEngine {
@@ -276,10 +278,12 @@ export class UciEngine {
fens
);
const accuracy = computeAccuracy(positions);
const estimatedElo: EstimatedElo = estimateEloFromEngineOutput(positions);
this.isReady = true;
return {
positions: positionsWithClassification,
estimatedElo,
accuracy,
settings: {
engine: this.engineName,

View File

@@ -1,8 +1,11 @@
import { Grid2 as Grid, Typography } from "@mui/material";
import { useAtomValue } from "jotai";
import { gameEvalAtom } from "../../states";
type props = {
params: "accurecy" | "rating";
};
export default function Accuracies() {
export default function Accuracies(props: props) {
const gameEval = useAtomValue(gameEvalAtom);
if (!gameEval) return null;
@@ -24,11 +27,14 @@ export default function Accuracies() {
fontWeight="bold"
border="1px solid #424242"
>
{`${gameEval?.accuracy.white.toFixed(1)} %`}
{props.params === "accurecy"
? `${gameEval?.accuracy.white.toFixed(1)} %`
: `${Math.round(gameEval?.estimatedElo.white as number)}`}
</Typography>
<Typography align="center">Accuracies</Typography>
<Typography align="center">
{props.params === "accurecy" ? "Accuracies" : "Estimated Elo"}
</Typography>
<Typography
align="center"
sx={{ backgroundColor: "black", color: "white" }}
@@ -38,7 +44,9 @@ export default function Accuracies() {
fontWeight="bold"
border="1px solid #424242"
>
{`${gameEval?.accuracy.black.toFixed(1)} %`}
{props.params === "accurecy"
? `${gameEval?.accuracy.black.toFixed(1)} %`
: `${Math.round(gameEval?.estimatedElo.black as number)}`}
</Typography>
</Grid>
);

View File

@@ -57,7 +57,8 @@ export default function AnalysisTab(props: GridProps) {
: { overflow: "hidden", overflowY: "auto", ...props.sx }
}
>
<Accuracies />
<Accuracies params={"accurecy"} />
<Accuracies params={"rating"} />
<MoveInfo />

View File

@@ -21,6 +21,10 @@ export interface Accuracy {
black: number;
}
export interface EstimatedElo {
white: number | null;
black: number | null;
}
export interface EngineSettings {
engine: EngineName;
depth: number;
@@ -31,6 +35,7 @@ export interface EngineSettings {
export interface GameEval {
positions: PositionEval[];
accuracy: Accuracy;
estimatedElo: EstimatedElo;
settings: EngineSettings;
}