feat : great & brilliant v0

This commit is contained in:
GuillaumeSD
2024-03-10 03:17:52 +01:00
parent 0997e9cf86
commit 7421258e4e
10 changed files with 227 additions and 32 deletions

BIN
public/icons/brilliant.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/icons/great.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,6 +1,6 @@
import { EvaluateGameParams, PositionEval } from "@/types/eval";
import { Game } from "@/types/game";
import { Chess } from "chess.js";
import { Chess, PieceSymbol } from "chess.js";
import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
@@ -76,12 +76,7 @@ export const moveLineUciToSan = (
return (moveUci: string): string => {
try {
const move = game.move({
from: moveUci.slice(0, 2),
to: moveUci.slice(2, 4),
promotion: moveUci.slice(4, 5) || undefined,
});
const move = game.move(uciMoveParams(moveUci));
return move.san;
} catch (e) {
return moveUci;
@@ -117,3 +112,68 @@ export const getWhoIsCheckmated = (fen: string): "w" | "b" | null => {
if (!game.isCheckmate()) return null;
return game.turn();
};
export const uciMoveParams = (
uciMove: string
): {
from: string;
to: string;
promotion?: string | undefined;
} => ({
from: uciMove.slice(0, 2),
to: uciMove.slice(2, 4),
promotion: uciMove.slice(4, 5) || undefined,
});
export const getIsPieceSacrifice = (
fen: string,
playedMove: string,
bestLinePvToPlay: string[]
): boolean => {
if (playedMove.slice(2, 4) !== bestLinePvToPlay[0].slice(2, 4)) return false;
const game = new Chess(fen);
const whiteToPlay = game.turn() === "w";
const startingMaterialDifference = getMaterialDifference(fen);
game.move(uciMoveParams(playedMove));
game.move(uciMoveParams(bestLinePvToPlay[0]));
const endingMaterialDifference = getMaterialDifference(game.fen());
const materialDiff = endingMaterialDifference - startingMaterialDifference;
const materialDiffPlayerRelative = whiteToPlay ? materialDiff : -materialDiff;
return materialDiffPlayerRelative < 0;
};
export const getMaterialDifference = (fen: string): number => {
const game = new Chess(fen);
const board = game.board().flat();
return board.reduce((acc, square) => {
if (!square) return acc;
const piece = square.type;
if (square.color === "w") {
return acc + getPieceValue(piece);
}
return acc - getPieceValue(piece);
}, 0);
};
const getPieceValue = (piece: PieceSymbol): number => {
switch (piece) {
case "p":
return 1;
case "n":
return 3;
case "b":
return 3;
case "r":
return 5;
case "q":
return 9;
default:
return 0;
}
};

View File

@@ -1,7 +1,11 @@
import { PositionEval } from "@/types/eval";
import { getPositionWinPercentage } from "./winPercentage";
import { LineEval, PositionEval } from "@/types/eval";
import {
getLineWinPercentage,
getPositionWinPercentage,
} from "./winPercentage";
import { MoveClassification } from "@/types/enums";
import { openings } from "@/data/openings";
import { getIsPieceSacrifice } from "@/lib/chess";
export const getMovesClassification = (
rawPositions: PositionEval[],
@@ -25,9 +29,54 @@ export const getMovesClassification = (
};
}
const uciMove = uciMoves[index - 1];
const playedMove = uciMoves[index - 1];
const bestMove = rawPositions[index - 1].bestMove;
if (uciMove === bestMove) {
const lastPositionAlternativeLine: LineEval | undefined = rawPositions[
index - 1
].lines.filter((line) => line.pv[0] !== playedMove)?.[0];
const lastPositionAlternativeLineWinPercentage = lastPositionAlternativeLine
? getLineWinPercentage(lastPositionAlternativeLine)
: undefined;
const bestLinePvToPlay = rawPosition.lines[0].pv;
const lastPositionWinPercentage = positionsWinPercentage[index - 1];
const positionWinPercentage = positionsWinPercentage[index];
const isWhiteMove = index % 2 === 1;
if (
isBrilliantMove(
positionWinPercentage,
isWhiteMove,
playedMove,
bestLinePvToPlay,
fens[index - 1]
)
) {
return {
...rawPosition,
opening: currentOpening,
moveClassification: MoveClassification.Brilliant,
};
}
if (
isGreatMove(
lastPositionWinPercentage,
positionWinPercentage,
isWhiteMove,
lastPositionAlternativeLineWinPercentage
)
) {
return {
...rawPosition,
opening: currentOpening,
moveClassification: MoveClassification.Great,
};
}
if (playedMove === bestMove) {
return {
...rawPosition,
opening: currentOpening,
@@ -35,11 +84,7 @@ export const getMovesClassification = (
};
}
const lastPositionWinPercentage = positionsWinPercentage[index - 1];
const positionWinPercentage = positionsWinPercentage[index];
const isWhiteMove = index % 2 === 1;
const moveClassification = getMoveClassification(
const moveClassification = getMoveBasicClassification(
lastPositionWinPercentage,
positionWinPercentage,
isWhiteMove
@@ -55,7 +100,7 @@ export const getMovesClassification = (
return positions;
};
const getMoveClassification = (
const getMoveBasicClassification = (
lastPositionWinPercentage: number,
positionWinPercentage: number,
isWhiteMove: boolean
@@ -70,3 +115,83 @@ const getMoveClassification = (
if (winPercentageDiff < -2) return MoveClassification.Good;
return MoveClassification.Excellent;
};
const isBrilliantMove = (
positionWinPercentage: number,
isWhiteMove: boolean,
playedMove: string,
bestLinePvToPlay: string[],
fen: string
): boolean => {
const isPieceSacrifice = getIsPieceSacrifice(
fen,
playedMove,
bestLinePvToPlay
);
if (!isPieceSacrifice) return false;
const isNotLosing = isWhiteMove
? positionWinPercentage > 50
: positionWinPercentage < 50;
const isAlternateCompletelyWinning = isWhiteMove
? positionWinPercentage > 70
: positionWinPercentage < 30;
return isNotLosing && !isAlternateCompletelyWinning;
};
const isGreatMove = (
lastPositionWinPercentage: number,
positionWinPercentage: number,
isWhiteMove: boolean,
lastPositionAlternativeLineWinPercentage: number | undefined
): boolean => {
if (!lastPositionAlternativeLineWinPercentage) return false;
const winPercentageDiff =
(positionWinPercentage - lastPositionWinPercentage) *
(isWhiteMove ? 1 : -1);
if (winPercentageDiff < -2) return false;
const hasChangedGameOutcome = getHasChangedGameOutcome(
lastPositionWinPercentage,
positionWinPercentage,
isWhiteMove
);
const isTheOnlyGoodMove = getIsTheOnlyGoodMove(
positionWinPercentage,
lastPositionAlternativeLineWinPercentage,
isWhiteMove
);
return hasChangedGameOutcome && isTheOnlyGoodMove;
};
const getHasChangedGameOutcome = (
lastPositionWinPercentage: number,
positionWinPercentage: number,
isWhiteMove: boolean
): boolean => {
const winPercentageDiff =
(positionWinPercentage - lastPositionWinPercentage) *
(isWhiteMove ? 1 : -1);
return (
winPercentageDiff > 10 &&
((lastPositionWinPercentage < 50 && positionWinPercentage > 50) ||
(lastPositionWinPercentage > 50 && positionWinPercentage < 50))
);
};
const getIsTheOnlyGoodMove = (
positionWinPercentage: number,
lastPositionAlternativeLineWinPercentage: number,
isWhiteMove: boolean
): boolean => {
const winPercentageDiff =
(positionWinPercentage - lastPositionAlternativeLineWinPercentage) *
(isWhiteMove ? 1 : -1);
return winPercentageDiff > 5;
};

View File

@@ -1,16 +1,20 @@
import { ceilsNumber } from "@/lib/helpers";
import { PositionEval } from "@/types/eval";
import { LineEval, PositionEval } from "@/types/eval";
export const getPositionWinPercentage = (position: PositionEval): number => {
if (position.lines[0].cp !== undefined) {
return getWinPercentageFromCp(position.lines[0].cp);
return getLineWinPercentage(position.lines[0]);
};
export const getLineWinPercentage = (line: LineEval): number => {
if (line.cp !== undefined) {
return getWinPercentageFromCp(line.cp);
}
if (position.lines[0].mate !== undefined) {
return getWinPercentageFromMate(position.lines[0].mate);
if (line.mate !== undefined) {
return getWinPercentageFromMate(line.mate);
}
throw new Error("No cp or mate in move");
throw new Error("No cp or mate in line");
};
const getWinPercentageFromMate = (mate: number): number => {

View File

@@ -38,7 +38,7 @@ export abstract class UciEngine {
this.throwErrorIfNotReady();
}
if (multiPv < 1 || multiPv > 6) {
if (multiPv < 2 || multiPv > 6) {
throw new Error(`Invalid MultiPV value : ${multiPv}`);
}

View File

@@ -58,8 +58,10 @@ SquareRenderer.displayName = "CustomSquareRenderer";
export default SquareRenderer;
export const moveClassificationColors: Record<MoveClassification, string> = {
[MoveClassification.Best]: "#3aab18",
[MoveClassification.Book]: "#d5a47d",
[MoveClassification.Brilliant]: "#26c2a3",
[MoveClassification.Great]: "#749bbf",
[MoveClassification.Best]: "#3aab18",
[MoveClassification.Excellent]: "#3aab18",
[MoveClassification.Good]: "#81b64c",
[MoveClassification.Inaccuracy]: "#f7c631",

View File

@@ -42,11 +42,13 @@ export default function MoveInfo() {
}
const moveClassificationLabels: Record<MoveClassification, string> = {
[MoveClassification.Blunder]: "a blunder",
[MoveClassification.Mistake]: "a mistake",
[MoveClassification.Inaccuracy]: "an inaccuracy",
[MoveClassification.Good]: "good",
[MoveClassification.Excellent]: "excellent",
[MoveClassification.Best]: "the best move",
[MoveClassification.Book]: "a book move",
[MoveClassification.Brilliant]: "brilliant !!",
[MoveClassification.Great]: "a great move !",
[MoveClassification.Best]: "the best move",
[MoveClassification.Excellent]: "excellent",
[MoveClassification.Good]: "good",
[MoveClassification.Inaccuracy]: "an inaccuracy",
[MoveClassification.Mistake]: "a mistake",
[MoveClassification.Blunder]: "a blunder",
};

View File

@@ -86,7 +86,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
label="Number of lines"
value={multiPv}
setValue={setMultiPv}
min={1}
min={2}
max={6}
xs={6}
/>

View File

@@ -15,4 +15,6 @@ export enum MoveClassification {
Excellent = "excellent",
Best = "best",
Book = "book",
Great = "great",
Brilliant = "brilliant",
}