feat : add move classification
This commit is contained in:
13606
src/data/openings.ts
Normal file
13606
src/data/openings.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,18 @@
|
|||||||
import { LineEval } from "@/types/eval";
|
import { EvaluateGameParams, LineEval } from "@/types/eval";
|
||||||
import { Game } from "@/types/game";
|
import { Game } from "@/types/game";
|
||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
|
|
||||||
export const getFens = (game: Chess): string[] => {
|
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
|
||||||
const history = game.history({ verbose: true });
|
const history = game.history({ verbose: true });
|
||||||
|
|
||||||
const fens = history.map((move) => move.before);
|
const fens = history.map((move) => move.before);
|
||||||
fens.push(history[history.length - 1].after);
|
fens.push(history[history.length - 1].after);
|
||||||
|
|
||||||
return fens;
|
const uciMoves = history.map(
|
||||||
|
(move) => move.from + move.to + (move.promotion || "")
|
||||||
|
);
|
||||||
|
|
||||||
|
return { fens, uciMoves };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGameFromPgn = (pgn: string): Chess => {
|
export const getGameFromPgn = (pgn: string): Chess => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getWeightedMean,
|
getWeightedMean,
|
||||||
} from "@/lib/helpers";
|
} from "@/lib/helpers";
|
||||||
import { Accuracy, MoveEval } from "@/types/eval";
|
import { Accuracy, MoveEval } from "@/types/eval";
|
||||||
|
import { getPositionWinPercentage } from "./winPercentage";
|
||||||
|
|
||||||
export const computeAccuracy = (moves: MoveEval[]): Accuracy => {
|
export const computeAccuracy = (moves: MoveEval[]): Accuracy => {
|
||||||
const movesWinPercentage = moves.map(getPositionWinPercentage);
|
const movesWinPercentage = moves.map(getPositionWinPercentage);
|
||||||
@@ -85,27 +86,3 @@ const getMovesAccuracy = (movesWinPercentage: number[]): number[] =>
|
|||||||
|
|
||||||
return Math.min(100, Math.max(0, rawAccuracy + 1));
|
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;
|
|
||||||
};
|
|
||||||
|
|||||||
72
src/lib/engine/helpers/moveClassification.ts
Normal file
72
src/lib/engine/helpers/moveClassification.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -5,7 +5,6 @@ export const parseEvaluationResults = (
|
|||||||
whiteToPlay: boolean
|
whiteToPlay: boolean
|
||||||
): MoveEval => {
|
): MoveEval => {
|
||||||
const parsedResults: MoveEval = {
|
const parsedResults: MoveEval = {
|
||||||
bestMove: "",
|
|
||||||
lines: [],
|
lines: [],
|
||||||
};
|
};
|
||||||
const tempResults: Record<string, LineEval> = {};
|
const tempResults: Record<string, LineEval> = {};
|
||||||
|
|||||||
26
src/lib/engine/helpers/winPercentage.ts
Normal file
26
src/lib/engine/helpers/winPercentage.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import {
|
import {
|
||||||
|
EvaluateGameParams,
|
||||||
EvaluatePositionWithUpdateParams,
|
EvaluatePositionWithUpdateParams,
|
||||||
GameEval,
|
GameEval,
|
||||||
MoveEval,
|
MoveEval,
|
||||||
@@ -8,6 +9,7 @@ import { parseEvaluationResults } from "./helpers/parseResults";
|
|||||||
import { computeAccuracy } from "./helpers/accuracy";
|
import { computeAccuracy } from "./helpers/accuracy";
|
||||||
import { getWhoIsCheckmated } from "../chess";
|
import { getWhoIsCheckmated } from "../chess";
|
||||||
import { getLichessEval } from "../lichess";
|
import { getLichessEval } from "../lichess";
|
||||||
|
import { getMovesClassification } from "./helpers/moveClassification";
|
||||||
|
|
||||||
export abstract class UciEngine {
|
export abstract class UciEngine {
|
||||||
private worker: Worker;
|
private worker: Worker;
|
||||||
@@ -93,11 +95,12 @@ export abstract class UciEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async evaluateGame(
|
public async evaluateGame({
|
||||||
fens: string[],
|
fens,
|
||||||
|
uciMoves,
|
||||||
depth = 16,
|
depth = 16,
|
||||||
multiPv = this.multiPv
|
multiPv = this.multiPv,
|
||||||
): Promise<GameEval> {
|
}: EvaluateGameParams): Promise<GameEval> {
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
await this.setMultiPv(multiPv);
|
await this.setMultiPv(multiPv);
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
@@ -110,7 +113,6 @@ export abstract class UciEngine {
|
|||||||
const whoIsCheckmated = getWhoIsCheckmated(fen);
|
const whoIsCheckmated = getWhoIsCheckmated(fen);
|
||||||
if (whoIsCheckmated) {
|
if (whoIsCheckmated) {
|
||||||
moves.push({
|
moves.push({
|
||||||
bestMove: "",
|
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
pv: [],
|
pv: [],
|
||||||
@@ -126,11 +128,16 @@ export abstract class UciEngine {
|
|||||||
moves.push(result);
|
moves.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const movesWithClassification = getMovesClassification(
|
||||||
|
moves,
|
||||||
|
uciMoves,
|
||||||
|
fens
|
||||||
|
);
|
||||||
const accuracy = computeAccuracy(moves);
|
const accuracy = computeAccuracy(moves);
|
||||||
|
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
return {
|
return {
|
||||||
moves: moves.slice(0, -1),
|
moves: movesWithClassification,
|
||||||
accuracy,
|
accuracy,
|
||||||
settings: {
|
settings: {
|
||||||
engine: this.engineName,
|
engine: this.engineName,
|
||||||
|
|||||||
@@ -37,9 +37,25 @@ export const getLichessEval = async (
|
|||||||
|
|
||||||
lines.sort(sortLines);
|
lines.sort(sortLines);
|
||||||
|
|
||||||
|
const bestMove = lines[0].pv[0];
|
||||||
|
const linesToKeep = lines.slice(0, multiPv);
|
||||||
|
|
||||||
|
const isWhiteToPlay = fen.split(" ")[1] === "w";
|
||||||
|
|
||||||
|
if (!isWhiteToPlay) {
|
||||||
|
return {
|
||||||
|
bestMove,
|
||||||
|
lines: linesToKeep.map((line) => ({
|
||||||
|
...line,
|
||||||
|
cp: line.cp ? -line.cp : line.cp,
|
||||||
|
mate: line.mate ? -line.mate : line.mate,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bestMove: lines[0].pv[0],
|
bestMove,
|
||||||
lines: lines.slice(0, multiPv),
|
lines: linesToKeep,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
import Board from "@/sections/analysis/board";
|
import Board from "@/sections/analysis/board";
|
||||||
import ReviewPanelBody from "@/sections/analysis/reviewPanelBody";
|
import ReviewPanelBody from "@/sections/analysis/reviewPanelBody";
|
||||||
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
||||||
@@ -55,6 +55,7 @@ export default function GameReport() {
|
|||||||
backgroundColor: "secondary.main",
|
backgroundColor: "secondary.main",
|
||||||
borderColor: "primary.main",
|
borderColor: "primary.main",
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
|
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
||||||
}}
|
}}
|
||||||
padding={3}
|
padding={3}
|
||||||
rowGap={3}
|
rowGap={3}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
showPlayerMoveArrowAtom,
|
showPlayerMoveArrowAtom,
|
||||||
} from "../states";
|
} from "../states";
|
||||||
import { Arrow, Square } from "react-chessboard/dist/chessboard/types";
|
import { Arrow, Square } from "react-chessboard/dist/chessboard/types";
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
import { useMemo, useRef } from "react";
|
import { useMemo, useRef } from "react";
|
||||||
import PlayerInfo from "./playerInfo";
|
import PlayerInfo from "./playerInfo";
|
||||||
import EvaluationBar from "./evaluationBar";
|
import EvaluationBar from "./evaluationBar";
|
||||||
@@ -25,12 +25,16 @@ export default function Board() {
|
|||||||
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
||||||
const currentMove = useAtomValue(currentMoveAtom);
|
const currentMove = useAtomValue(currentMoveAtom);
|
||||||
|
|
||||||
const onPieceDrop = (source: Square, target: Square): boolean => {
|
const onPieceDrop = (
|
||||||
|
source: Square,
|
||||||
|
target: Square,
|
||||||
|
piece: string
|
||||||
|
): boolean => {
|
||||||
try {
|
try {
|
||||||
const result = makeBoardMove({
|
const result = makeBoardMove({
|
||||||
from: source,
|
from: source,
|
||||||
to: target,
|
to: target,
|
||||||
promotion: "q", // TODO: Let the user choose the promotion
|
promotion: piece[1]?.toLowerCase() ?? "q",
|
||||||
});
|
});
|
||||||
|
|
||||||
return !!result;
|
return !!result;
|
||||||
@@ -42,7 +46,7 @@ export default function Board() {
|
|||||||
const customArrows: Arrow[] = useMemo(() => {
|
const customArrows: Arrow[] = useMemo(() => {
|
||||||
const arrows: Arrow[] = [];
|
const arrows: Arrow[] = [];
|
||||||
|
|
||||||
if (currentMove?.lastEval && showBestMoveArrow) {
|
if (currentMove?.lastEval?.bestMove && showBestMoveArrow) {
|
||||||
const bestMoveArrow = [
|
const bestMoveArrow = [
|
||||||
currentMove.lastEval.bestMove.slice(0, 2),
|
currentMove.lastEval.bestMove.slice(0, 2),
|
||||||
currentMove.lastEval.bestMove.slice(2, 4),
|
currentMove.lastEval.bestMove.slice(2, 4),
|
||||||
@@ -108,7 +112,10 @@ export default function Board() {
|
|||||||
onPieceDrop={onPieceDrop}
|
onPieceDrop={onPieceDrop}
|
||||||
boardOrientation={boardOrientation ? "white" : "black"}
|
boardOrientation={boardOrientation ? "white" : "black"}
|
||||||
customArrows={customArrows}
|
customArrows={customArrows}
|
||||||
customBoardStyle={{ borderRadius: "5px" }}
|
customBoardStyle={{
|
||||||
|
borderRadius: "5px",
|
||||||
|
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
|
||||||
import { Grid, Typography } from "@mui/material";
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { boardAtom } from "../states";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { moveLineUciToSan } from "@/lib/chess";
|
|
||||||
|
|
||||||
export default function BestMove() {
|
|
||||||
const move = useCurrentMove();
|
|
||||||
const board = useAtomValue(boardAtom);
|
|
||||||
|
|
||||||
const bestMove = move?.lastEval?.bestMove;
|
|
||||||
|
|
||||||
const bestMoveSan = useMemo(() => {
|
|
||||||
if (!bestMove) return undefined;
|
|
||||||
|
|
||||||
const lastPosition = board.history({ verbose: true }).at(-1)?.before;
|
|
||||||
if (!lastPosition) return undefined;
|
|
||||||
|
|
||||||
return moveLineUciToSan(lastPosition)(bestMove);
|
|
||||||
}, [bestMove, board]);
|
|
||||||
|
|
||||||
if (!bestMoveSan) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography align="center">{`${bestMoveSan} was the best move`}</Typography>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,8 @@ import { LineEval } from "@/types/eval";
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import EngineSettingsButton from "@/sections/engineSettings/engineSettingsButton";
|
import EngineSettingsButton from "@/sections/engineSettings/engineSettingsButton";
|
||||||
import Accuracies from "./accuracies";
|
import Accuracies from "./accuracies";
|
||||||
import BestMove from "./bestMove";
|
import MoveInfo from "./moveInfo";
|
||||||
|
import Opening from "./opening";
|
||||||
|
|
||||||
export default function ReviewPanelBody() {
|
export default function ReviewPanelBody() {
|
||||||
const linesNumber = useAtomValue(engineMultiPvAtom);
|
const linesNumber = useAtomValue(engineMultiPvAtom);
|
||||||
@@ -20,7 +21,10 @@ export default function ReviewPanelBody() {
|
|||||||
const gameHistory = game.history();
|
const gameHistory = game.history();
|
||||||
|
|
||||||
const isGameOver =
|
const isGameOver =
|
||||||
gameHistory.length > 0 && boardHistory.join() === gameHistory.join();
|
boardHistory.length > 0 &&
|
||||||
|
(board.isCheckmate() ||
|
||||||
|
board.isDraw() ||
|
||||||
|
boardHistory.join() === gameHistory.join());
|
||||||
|
|
||||||
const linesSkeleton: LineEval[] = Array.from({ length: linesNumber }).map(
|
const linesSkeleton: LineEval[] = Array.from({ length: linesNumber }).map(
|
||||||
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
|
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
|
||||||
@@ -73,7 +77,9 @@ export default function ReviewPanelBody() {
|
|||||||
|
|
||||||
<Accuracies />
|
<Accuracies />
|
||||||
|
|
||||||
<BestMove />
|
<MoveInfo />
|
||||||
|
|
||||||
|
<Opening />
|
||||||
|
|
||||||
{isGameOver && (
|
{isGameOver && (
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|||||||
53
src/sections/analysis/reviewPanelBody/moveInfo.tsx
Normal file
53
src/sections/analysis/reviewPanelBody/moveInfo.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
||||||
|
import { Grid, Typography } from "@mui/material";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { boardAtom } from "../states";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { moveLineUciToSan } from "@/lib/chess";
|
||||||
|
import { MoveClassification } from "@/types/enums";
|
||||||
|
|
||||||
|
export default function MoveInfo() {
|
||||||
|
const move = useCurrentMove();
|
||||||
|
const board = useAtomValue(boardAtom);
|
||||||
|
|
||||||
|
const bestMove = move?.lastEval?.bestMove;
|
||||||
|
|
||||||
|
const bestMoveSan = useMemo(() => {
|
||||||
|
if (!bestMove) return undefined;
|
||||||
|
|
||||||
|
const lastPosition = board.history({ verbose: true }).at(-1)?.before;
|
||||||
|
if (!lastPosition) return undefined;
|
||||||
|
|
||||||
|
return moveLineUciToSan(lastPosition)(bestMove);
|
||||||
|
}, [bestMove, board]);
|
||||||
|
|
||||||
|
if (!bestMoveSan) return null;
|
||||||
|
|
||||||
|
const moveClassification = move.eval?.moveClassification;
|
||||||
|
const moveLabel = moveClassification
|
||||||
|
? `${move.san} is ${moveClassificationLabels[moveClassification]}`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const bestMoveLabel =
|
||||||
|
moveClassification === MoveClassification.Best ||
|
||||||
|
moveClassification === MoveClassification.Book
|
||||||
|
? null
|
||||||
|
: `${bestMoveSan} was the best move`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid item container columnGap={5} xs={12} justifyContent="center">
|
||||||
|
{moveLabel && <Typography align="center">{moveLabel}</Typography>}
|
||||||
|
{bestMoveLabel && <Typography align="center">{bestMoveLabel}</Typography>}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]: "an opening move",
|
||||||
|
};
|
||||||
15
src/sections/analysis/reviewPanelBody/opening.tsx
Normal file
15
src/sections/analysis/reviewPanelBody/opening.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
||||||
|
import { Grid, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
export default function Opening() {
|
||||||
|
const move = useCurrentMove();
|
||||||
|
|
||||||
|
const opening = move?.eval?.opening;
|
||||||
|
if (!opening) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography align="center">{opening}</Typography>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
gameEvalAtom,
|
gameEvalAtom,
|
||||||
} from "../states";
|
} from "../states";
|
||||||
import { useAtomValue, useSetAtom } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
import { getFens } from "@/lib/chess";
|
import { getEvaluateGameParams } from "@/lib/chess";
|
||||||
import { useGameDatabase } from "@/hooks/useGameDatabase";
|
import { useGameDatabase } from "@/hooks/useGameDatabase";
|
||||||
import { LoadingButton } from "@mui/lab";
|
import { LoadingButton } from "@mui/lab";
|
||||||
import { useEngine } from "@/hooks/useEngine";
|
import { useEngine } from "@/hooks/useEngine";
|
||||||
@@ -27,18 +27,22 @@ export default function AnalyzeButton() {
|
|||||||
engine?.isReady() && game.history().length > 0 && !evaluationInProgress;
|
engine?.isReady() && game.history().length > 0 && !evaluationInProgress;
|
||||||
|
|
||||||
const handleAnalyze = async () => {
|
const handleAnalyze = async () => {
|
||||||
const gameFens = getFens(game);
|
const params = getEvaluateGameParams(game);
|
||||||
if (!engine?.isReady() || gameFens.length === 0 || evaluationInProgress) {
|
if (
|
||||||
|
!engine?.isReady() ||
|
||||||
|
params.fens.length === 0 ||
|
||||||
|
evaluationInProgress
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEvaluationInProgress(true);
|
setEvaluationInProgress(true);
|
||||||
|
|
||||||
const newGameEval = await engine.evaluateGame(
|
const newGameEval = await engine.evaluateGame({
|
||||||
gameFens,
|
...params,
|
||||||
engineDepth,
|
depth: engineDepth,
|
||||||
engineMultiPv
|
multiPv: engineMultiPv,
|
||||||
);
|
});
|
||||||
|
|
||||||
setEval(newGameEval);
|
setEval(newGameEval);
|
||||||
setEvaluationInProgress(false);
|
setEvaluationInProgress(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import LoadGameButton from "../../loadGame/loadGameButton";
|
import LoadGameButton from "../../loadGame/loadGameButton";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
import {
|
import {
|
||||||
boardAtom,
|
boardAtom,
|
||||||
boardOrientationAtom,
|
boardOrientationAtom,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Icon } from "@iconify/react";
|
|||||||
import { Grid, IconButton, Tooltip } from "@mui/material";
|
import { Grid, IconButton, Tooltip } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom, gameAtom } from "../states";
|
import { boardAtom, gameAtom } from "../states";
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
|
|
||||||
export default function GoToLastPositionButton() {
|
export default function GoToLastPositionButton() {
|
||||||
const { setPgn: setBoardPgn } = useChessActions(boardAtom);
|
const { setPgn: setBoardPgn } = useChessActions(boardAtom);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Grid, IconButton, Tooltip } from "@mui/material";
|
|||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom } from "../states";
|
import { boardAtom } from "../states";
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
import FlipBoardButton from "./flipBoardButton";
|
import FlipBoardButton from "./flipBoardButton";
|
||||||
import NextMoveButton from "./nextMoveButton";
|
import NextMoveButton from "./nextMoveButton";
|
||||||
import GoToLastPositionButton from "./goToLastPositionButton";
|
import GoToLastPositionButton from "./goToLastPositionButton";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Icon } from "@iconify/react";
|
|||||||
import { Grid, IconButton, Tooltip } from "@mui/material";
|
import { Grid, IconButton, Tooltip } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom, gameAtom } from "../states";
|
import { boardAtom, gameAtom } from "../states";
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
|
|
||||||
export default function NextMoveButton() {
|
export default function NextMoveButton() {
|
||||||
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
||||||
|
|||||||
@@ -6,3 +6,13 @@ export enum GameOrigin {
|
|||||||
export enum EngineName {
|
export enum EngineName {
|
||||||
Stockfish16 = "stockfish_16",
|
Stockfish16 = "stockfish_16",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MoveClassification {
|
||||||
|
Blunder = "blunder",
|
||||||
|
Mistake = "mistake",
|
||||||
|
Inaccuracy = "inaccuracy",
|
||||||
|
Good = "good",
|
||||||
|
Excellent = "excellent",
|
||||||
|
Best = "best",
|
||||||
|
Book = "book",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Move } from "chess.js";
|
import { Move } from "chess.js";
|
||||||
import { EngineName } from "./enums";
|
import { EngineName, MoveClassification } from "./enums";
|
||||||
|
|
||||||
export interface MoveEval {
|
export interface MoveEval {
|
||||||
bestMove: string;
|
bestMove?: string;
|
||||||
|
moveClassification?: MoveClassification;
|
||||||
|
opening?: string;
|
||||||
lines: LineEval[];
|
lines: LineEval[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,3 +45,10 @@ export type CurrentMove = Partial<Move> & {
|
|||||||
eval?: MoveEval;
|
eval?: MoveEval;
|
||||||
lastEval?: MoveEval;
|
lastEval?: MoveEval;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface EvaluateGameParams {
|
||||||
|
fens: string[];
|
||||||
|
uciMoves: string[];
|
||||||
|
depth?: number;
|
||||||
|
multiPv?: number;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user