feat : great & brilliant v0
This commit is contained in:
BIN
public/icons/brilliant.png
Normal file
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
BIN
public/icons/great.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import { EvaluateGameParams, PositionEval } from "@/types/eval";
|
import { EvaluateGameParams, PositionEval } from "@/types/eval";
|
||||||
import { Game } from "@/types/game";
|
import { Game } from "@/types/game";
|
||||||
import { Chess } from "chess.js";
|
import { Chess, PieceSymbol } from "chess.js";
|
||||||
import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
|
import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
|
||||||
|
|
||||||
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
|
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
|
||||||
@@ -76,12 +76,7 @@ export const moveLineUciToSan = (
|
|||||||
|
|
||||||
return (moveUci: string): string => {
|
return (moveUci: string): string => {
|
||||||
try {
|
try {
|
||||||
const move = game.move({
|
const move = game.move(uciMoveParams(moveUci));
|
||||||
from: moveUci.slice(0, 2),
|
|
||||||
to: moveUci.slice(2, 4),
|
|
||||||
promotion: moveUci.slice(4, 5) || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return move.san;
|
return move.san;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return moveUci;
|
return moveUci;
|
||||||
@@ -117,3 +112,68 @@ export const getWhoIsCheckmated = (fen: string): "w" | "b" | null => {
|
|||||||
if (!game.isCheckmate()) return null;
|
if (!game.isCheckmate()) return null;
|
||||||
return game.turn();
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { PositionEval } from "@/types/eval";
|
import { LineEval, PositionEval } from "@/types/eval";
|
||||||
import { getPositionWinPercentage } from "./winPercentage";
|
import {
|
||||||
|
getLineWinPercentage,
|
||||||
|
getPositionWinPercentage,
|
||||||
|
} from "./winPercentage";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
import { openings } from "@/data/openings";
|
import { openings } from "@/data/openings";
|
||||||
|
import { getIsPieceSacrifice } from "@/lib/chess";
|
||||||
|
|
||||||
export const getMovesClassification = (
|
export const getMovesClassification = (
|
||||||
rawPositions: PositionEval[],
|
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;
|
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 {
|
return {
|
||||||
...rawPosition,
|
...rawPosition,
|
||||||
opening: currentOpening,
|
opening: currentOpening,
|
||||||
@@ -35,11 +84,7 @@ export const getMovesClassification = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastPositionWinPercentage = positionsWinPercentage[index - 1];
|
const moveClassification = getMoveBasicClassification(
|
||||||
const positionWinPercentage = positionsWinPercentage[index];
|
|
||||||
const isWhiteMove = index % 2 === 1;
|
|
||||||
|
|
||||||
const moveClassification = getMoveClassification(
|
|
||||||
lastPositionWinPercentage,
|
lastPositionWinPercentage,
|
||||||
positionWinPercentage,
|
positionWinPercentage,
|
||||||
isWhiteMove
|
isWhiteMove
|
||||||
@@ -55,7 +100,7 @@ export const getMovesClassification = (
|
|||||||
return positions;
|
return positions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMoveClassification = (
|
const getMoveBasicClassification = (
|
||||||
lastPositionWinPercentage: number,
|
lastPositionWinPercentage: number,
|
||||||
positionWinPercentage: number,
|
positionWinPercentage: number,
|
||||||
isWhiteMove: boolean
|
isWhiteMove: boolean
|
||||||
@@ -70,3 +115,83 @@ const getMoveClassification = (
|
|||||||
if (winPercentageDiff < -2) return MoveClassification.Good;
|
if (winPercentageDiff < -2) return MoveClassification.Good;
|
||||||
return MoveClassification.Excellent;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { ceilsNumber } from "@/lib/helpers";
|
import { ceilsNumber } from "@/lib/helpers";
|
||||||
import { PositionEval } from "@/types/eval";
|
import { LineEval, PositionEval } from "@/types/eval";
|
||||||
|
|
||||||
export const getPositionWinPercentage = (position: PositionEval): number => {
|
export const getPositionWinPercentage = (position: PositionEval): number => {
|
||||||
if (position.lines[0].cp !== undefined) {
|
return getLineWinPercentage(position.lines[0]);
|
||||||
return getWinPercentageFromCp(position.lines[0].cp);
|
};
|
||||||
|
|
||||||
|
export const getLineWinPercentage = (line: LineEval): number => {
|
||||||
|
if (line.cp !== undefined) {
|
||||||
|
return getWinPercentageFromCp(line.cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position.lines[0].mate !== undefined) {
|
if (line.mate !== undefined) {
|
||||||
return getWinPercentageFromMate(position.lines[0].mate);
|
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 => {
|
const getWinPercentageFromMate = (mate: number): number => {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export abstract class UciEngine {
|
|||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiPv < 1 || multiPv > 6) {
|
if (multiPv < 2 || multiPv > 6) {
|
||||||
throw new Error(`Invalid MultiPV value : ${multiPv}`);
|
throw new Error(`Invalid MultiPV value : ${multiPv}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,10 @@ SquareRenderer.displayName = "CustomSquareRenderer";
|
|||||||
export default SquareRenderer;
|
export default SquareRenderer;
|
||||||
|
|
||||||
export const moveClassificationColors: Record<MoveClassification, string> = {
|
export const moveClassificationColors: Record<MoveClassification, string> = {
|
||||||
[MoveClassification.Best]: "#3aab18",
|
|
||||||
[MoveClassification.Book]: "#d5a47d",
|
[MoveClassification.Book]: "#d5a47d",
|
||||||
|
[MoveClassification.Brilliant]: "#26c2a3",
|
||||||
|
[MoveClassification.Great]: "#749bbf",
|
||||||
|
[MoveClassification.Best]: "#3aab18",
|
||||||
[MoveClassification.Excellent]: "#3aab18",
|
[MoveClassification.Excellent]: "#3aab18",
|
||||||
[MoveClassification.Good]: "#81b64c",
|
[MoveClassification.Good]: "#81b64c",
|
||||||
[MoveClassification.Inaccuracy]: "#f7c631",
|
[MoveClassification.Inaccuracy]: "#f7c631",
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ export default function MoveInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const moveClassificationLabels: Record<MoveClassification, string> = {
|
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.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",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
|||||||
label="Number of lines"
|
label="Number of lines"
|
||||||
value={multiPv}
|
value={multiPv}
|
||||||
setValue={setMultiPv}
|
setValue={setMultiPv}
|
||||||
min={1}
|
min={2}
|
||||||
max={6}
|
max={6}
|
||||||
xs={6}
|
xs={6}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ export enum MoveClassification {
|
|||||||
Excellent = "excellent",
|
Excellent = "excellent",
|
||||||
Best = "best",
|
Best = "best",
|
||||||
Book = "book",
|
Book = "book",
|
||||||
|
Great = "great",
|
||||||
|
Brilliant = "brilliant",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user