diff --git a/src/lib/engine/helpers/estimateElo.ts b/src/lib/engine/helpers/estimateElo.ts
new file mode 100644
index 0000000..3d3d6cb
--- /dev/null
+++ b/src/lib/engine/helpers/estimateElo.ts
@@ -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 };
+ }
+};
diff --git a/src/lib/engine/uciEngine.ts b/src/lib/engine/uciEngine.ts
index 8469705..942b75a 100644
--- a/src/lib/engine/uciEngine.ts
+++ b/src/lib/engine/uciEngine.ts
@@ -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,
diff --git a/src/sections/analysis/panelBody/analysisTab/accuracies.tsx b/src/sections/analysis/panelBody/analysisTab/accuracies.tsx
index 70894b3..2dbad7d 100644
--- a/src/sections/analysis/panelBody/analysisTab/accuracies.tsx
+++ b/src/sections/analysis/panelBody/analysisTab/accuracies.tsx
@@ -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)}`}
- Accuracies
-
+
+ {props.params === "accurecy" ? "Accuracies" : "Estimated Elo"}
+
- {`${gameEval?.accuracy.black.toFixed(1)} %`}
+ {props.params === "accurecy"
+ ? `${gameEval?.accuracy.black.toFixed(1)} %`
+ : `${Math.round(gameEval?.estimatedElo.black as number)}`}
);
diff --git a/src/sections/analysis/panelBody/analysisTab/index.tsx b/src/sections/analysis/panelBody/analysisTab/index.tsx
index bf61ef2..02f2755 100644
--- a/src/sections/analysis/panelBody/analysisTab/index.tsx
+++ b/src/sections/analysis/panelBody/analysisTab/index.tsx
@@ -57,7 +57,8 @@ export default function AnalysisTab(props: GridProps) {
: { overflow: "hidden", overflowY: "auto", ...props.sx }
}
>
-
+
+
diff --git a/src/types/eval.ts b/src/types/eval.ts
index 7c51fab..67ebd67 100644
--- a/src/types/eval.ts
+++ b/src/types/eval.ts
@@ -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;
}