feat : add move classification

This commit is contained in:
GuillaumeSD
2024-03-04 01:56:17 +01:00
parent 9d11b0006e
commit 4975ecfdd1
22 changed files with 13872 additions and 89 deletions

View File

@@ -5,6 +5,7 @@ import {
getWeightedMean,
} from "@/lib/helpers";
import { Accuracy, MoveEval } from "@/types/eval";
import { getPositionWinPercentage } from "./winPercentage";
export const computeAccuracy = (moves: MoveEval[]): Accuracy => {
const movesWinPercentage = moves.map(getPositionWinPercentage);
@@ -85,27 +86,3 @@ const getMovesAccuracy = (movesWinPercentage: number[]): number[] =>
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;
};

View File

@@ -0,0 +1,72 @@
import { MoveEval } from "@/types/eval";
import { getPositionWinPercentage } from "./winPercentage";
import { MoveClassification } from "@/types/enums";
import { openings } from "@/data/openings";
export const getMovesClassification = (
rawMoves: MoveEval[],
uciMoves: string[],
fens: string[]
): MoveEval[] => {
const positionsWinPercentage = rawMoves.map(getPositionWinPercentage);
let currentOpening: string | undefined = undefined;
const moves = rawMoves.map((rawMove, index) => {
if (index === 0) return rawMove;
const currentFen = fens[index].split(" ")[0];
const opening = openings.find((opening) => opening.fen === currentFen);
if (opening) {
currentOpening = opening.name;
return {
...rawMove,
opening: opening.name,
moveClassification: MoveClassification.Book,
};
}
const uciMove = uciMoves[index - 1];
const bestMove = rawMoves[index - 1].bestMove;
if (uciMove === bestMove) {
return {
...rawMove,
opening: currentOpening,
moveClassification: MoveClassification.Best,
};
}
const lastPositionWinPercentage = positionsWinPercentage[index - 1];
const positionWinPercentage = positionsWinPercentage[index];
const isWhiteMove = index % 2 === 1;
const moveClassification = getMoveClassification(
lastPositionWinPercentage,
positionWinPercentage,
isWhiteMove
);
return {
...rawMove,
opening: currentOpening,
moveClassification,
};
});
return moves;
};
const getMoveClassification = (
lastPositionWinPercentage: number,
positionWinPercentage: number,
isWhiteMove: boolean
): MoveClassification => {
const winPercentageDiff =
(positionWinPercentage - lastPositionWinPercentage) *
(isWhiteMove ? 1 : -1);
if (winPercentageDiff < -15) return MoveClassification.Blunder;
if (winPercentageDiff < -10) return MoveClassification.Mistake;
if (winPercentageDiff < -5) return MoveClassification.Inaccuracy;
if (winPercentageDiff < 0) return MoveClassification.Good;
return MoveClassification.Excellent;
};

View File

@@ -5,7 +5,6 @@ export const parseEvaluationResults = (
whiteToPlay: boolean
): MoveEval => {
const parsedResults: MoveEval = {
bestMove: "",
lines: [],
};
const tempResults: Record<string, LineEval> = {};

View File

@@ -0,0 +1,26 @@
import { ceilsNumber } from "@/lib/helpers";
import { MoveEval } from "@/types/eval";
export 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;
};

View File

@@ -1,5 +1,6 @@
import { EngineName } from "@/types/enums";
import {
EvaluateGameParams,
EvaluatePositionWithUpdateParams,
GameEval,
MoveEval,
@@ -8,6 +9,7 @@ import { parseEvaluationResults } from "./helpers/parseResults";
import { computeAccuracy } from "./helpers/accuracy";
import { getWhoIsCheckmated } from "../chess";
import { getLichessEval } from "../lichess";
import { getMovesClassification } from "./helpers/moveClassification";
export abstract class UciEngine {
private worker: Worker;
@@ -93,11 +95,12 @@ export abstract class UciEngine {
});
}
public async evaluateGame(
fens: string[],
public async evaluateGame({
fens,
uciMoves,
depth = 16,
multiPv = this.multiPv
): Promise<GameEval> {
multiPv = this.multiPv,
}: EvaluateGameParams): Promise<GameEval> {
this.throwErrorIfNotReady();
await this.setMultiPv(multiPv);
this.ready = false;
@@ -110,7 +113,6 @@ export abstract class UciEngine {
const whoIsCheckmated = getWhoIsCheckmated(fen);
if (whoIsCheckmated) {
moves.push({
bestMove: "",
lines: [
{
pv: [],
@@ -126,11 +128,16 @@ export abstract class UciEngine {
moves.push(result);
}
const movesWithClassification = getMovesClassification(
moves,
uciMoves,
fens
);
const accuracy = computeAccuracy(moves);
this.ready = true;
return {
moves: moves.slice(0, -1),
moves: movesWithClassification,
accuracy,
settings: {
engine: this.engineName,