feat : add move classification icons
BIN
public/icons/best.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/blunder.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icons/book.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/excellent.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/good.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/icons/inaccuracy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/mistake.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
boardAtom,
|
boardAtom,
|
||||||
currentMoveAtom,
|
currentPositionAtom,
|
||||||
engineDepthAtom,
|
engineDepthAtom,
|
||||||
engineMultiPvAtom,
|
engineMultiPvAtom,
|
||||||
gameAtom,
|
gameAtom,
|
||||||
gameEvalAtom,
|
gameEvalAtom,
|
||||||
} from "@/sections/analysis/states";
|
} from "@/sections/analysis/states";
|
||||||
import { CurrentMove, MoveEval } from "@/types/eval";
|
import { CurrentPosition, PositionEval } from "@/types/eval";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useEngine } from "./useEngine";
|
import { useEngine } from "./useEngine";
|
||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
|
|
||||||
export const useCurrentMove = (engineName?: EngineName) => {
|
export const useCurrentPosition = (engineName?: EngineName) => {
|
||||||
const [currentMove, setCurrentMove] = useAtom(currentMoveAtom);
|
const [currentPosition, setCurrentPosition] = useAtom(currentPositionAtom);
|
||||||
const engine = useEngine(engineName);
|
const engine = useEngine(engineName);
|
||||||
const gameEval = useAtomValue(gameEvalAtom);
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
@@ -22,8 +22,8 @@ export const useCurrentMove = (engineName?: EngineName) => {
|
|||||||
const multiPv = useAtomValue(engineMultiPvAtom);
|
const multiPv = useAtomValue(engineMultiPvAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const move: CurrentMove = {
|
const position: CurrentPosition = {
|
||||||
...board.history({ verbose: true }).at(-1),
|
lastMove: board.history({ verbose: true }).at(-1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (gameEval) {
|
if (gameEval) {
|
||||||
@@ -36,15 +36,15 @@ export const useCurrentMove = (engineName?: EngineName) => {
|
|||||||
) {
|
) {
|
||||||
const evalIndex = board.history().length;
|
const evalIndex = board.history().length;
|
||||||
|
|
||||||
move.eval = gameEval.moves[evalIndex];
|
position.eval = gameEval.positions[evalIndex];
|
||||||
move.lastEval =
|
position.lastEval =
|
||||||
evalIndex > 0 ? gameEval.moves[evalIndex - 1] : undefined;
|
evalIndex > 0 ? gameEval.positions[evalIndex - 1] : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!move.eval && engine?.isReady()) {
|
if (!position.eval && engine?.isReady()) {
|
||||||
const setPartialEval = (moveEval: MoveEval) => {
|
const setPartialEval = (positionEval: PositionEval) => {
|
||||||
setCurrentMove({ ...move, eval: moveEval });
|
setCurrentPosition({ ...position, eval: positionEval });
|
||||||
};
|
};
|
||||||
|
|
||||||
engine.evaluatePositionWithUpdate({
|
engine.evaluatePositionWithUpdate({
|
||||||
@@ -55,8 +55,8 @@ export const useCurrentMove = (engineName?: EngineName) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentMove(move);
|
setCurrentPosition(position);
|
||||||
}, [gameEval, board, game, engine, depth, multiPv, setCurrentMove]);
|
}, [gameEval, board, game, engine, depth, multiPv, setCurrentPosition]);
|
||||||
|
|
||||||
return currentMove;
|
return currentPosition;
|
||||||
};
|
};
|
||||||
73
src/hooks/useSquareRenderer.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { showPlayerMoveIconAtom } from "@/sections/analysis/states";
|
||||||
|
import { MoveClassification } from "@/types/enums";
|
||||||
|
import { CurrentPosition } from "@/types/eval";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { CSSProperties, forwardRef, useMemo } from "react";
|
||||||
|
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
|
||||||
|
|
||||||
|
export const useSquareRenderer = (position: CurrentPosition) => {
|
||||||
|
const showPlayerMoveIcon = useAtomValue(showPlayerMoveIconAtom);
|
||||||
|
|
||||||
|
const CustomSquareRenderer = useMemo(() => {
|
||||||
|
const fromSquare = position.lastMove?.from;
|
||||||
|
const toSquare = position.lastMove?.to;
|
||||||
|
const moveClassification = position?.eval?.moveClassification;
|
||||||
|
|
||||||
|
if (!showPlayerMoveIcon || !moveClassification || !fromSquare || !toSquare)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
const squareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const { children, square, style } = props;
|
||||||
|
|
||||||
|
const customSquareStyle: CSSProperties | undefined =
|
||||||
|
fromSquare === square || toSquare === square
|
||||||
|
? {
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: moveClassificationColors[moveClassification],
|
||||||
|
opacity: 0.5,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} style={{ ...style, position: "relative" }}>
|
||||||
|
{children}
|
||||||
|
{customSquareStyle && <div style={customSquareStyle} />}
|
||||||
|
{square === toSquare && (
|
||||||
|
<Image
|
||||||
|
src={`/icons/${moveClassification}.png`}
|
||||||
|
alt="move-icon"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: -12,
|
||||||
|
right: -12,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
squareRenderer.displayName = "CustomSquareRenderer";
|
||||||
|
|
||||||
|
return squareRenderer;
|
||||||
|
}, [showPlayerMoveIcon, position]);
|
||||||
|
|
||||||
|
return CustomSquareRenderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moveClassificationColors: Record<MoveClassification, string> = {
|
||||||
|
[MoveClassification.Best]: "#3aab18",
|
||||||
|
[MoveClassification.Book]: "#d5a47d",
|
||||||
|
[MoveClassification.Excellent]: "#3aab18",
|
||||||
|
[MoveClassification.Good]: "#81b64c",
|
||||||
|
[MoveClassification.Inaccuracy]: "#f7c631",
|
||||||
|
[MoveClassification.Mistake]: "#ffa459",
|
||||||
|
[MoveClassification.Blunder]: "#fa412d",
|
||||||
|
};
|
||||||
@@ -4,15 +4,15 @@ import {
|
|||||||
getStandardDeviation,
|
getStandardDeviation,
|
||||||
getWeightedMean,
|
getWeightedMean,
|
||||||
} from "@/lib/helpers";
|
} from "@/lib/helpers";
|
||||||
import { Accuracy, MoveEval } from "@/types/eval";
|
import { Accuracy, PositionEval } from "@/types/eval";
|
||||||
import { getPositionWinPercentage } from "./winPercentage";
|
import { getPositionWinPercentage } from "./winPercentage";
|
||||||
|
|
||||||
export const computeAccuracy = (moves: MoveEval[]): Accuracy => {
|
export const computeAccuracy = (positions: PositionEval[]): Accuracy => {
|
||||||
const movesWinPercentage = moves.map(getPositionWinPercentage);
|
const positionsWinPercentage = positions.map(getPositionWinPercentage);
|
||||||
|
|
||||||
const weights = getAccuracyWeights(movesWinPercentage);
|
const weights = getAccuracyWeights(positionsWinPercentage);
|
||||||
|
|
||||||
const movesAccuracy = getMovesAccuracy(movesWinPercentage);
|
const movesAccuracy = getMovesAccuracy(positionsWinPercentage);
|
||||||
|
|
||||||
const whiteAccuracy = getPlayerAccuracy(movesAccuracy, weights, "white");
|
const whiteAccuracy = getPlayerAccuracy(movesAccuracy, weights, "white");
|
||||||
const blackAccuracy = getPlayerAccuracy(movesAccuracy, weights, "black");
|
const blackAccuracy = getPlayerAccuracy(movesAccuracy, weights, "black");
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { MoveEval } from "@/types/eval";
|
import { PositionEval } from "@/types/eval";
|
||||||
import { getPositionWinPercentage } from "./winPercentage";
|
import { getPositionWinPercentage } from "./winPercentage";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
import { openings } from "@/data/openings";
|
import { openings } from "@/data/openings";
|
||||||
|
|
||||||
export const getMovesClassification = (
|
export const getMovesClassification = (
|
||||||
rawMoves: MoveEval[],
|
rawMoves: PositionEval[],
|
||||||
uciMoves: string[],
|
uciMoves: string[],
|
||||||
fens: string[]
|
fens: string[]
|
||||||
): MoveEval[] => {
|
): PositionEval[] => {
|
||||||
const positionsWinPercentage = rawMoves.map(getPositionWinPercentage);
|
const positionsWinPercentage = rawMoves.map(getPositionWinPercentage);
|
||||||
let currentOpening: string | undefined = undefined;
|
let currentOpening: string | undefined = undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { LineEval, MoveEval } from "@/types/eval";
|
import { LineEval, PositionEval } from "@/types/eval";
|
||||||
|
|
||||||
export const parseEvaluationResults = (
|
export const parseEvaluationResults = (
|
||||||
results: string[],
|
results: string[],
|
||||||
whiteToPlay: boolean
|
whiteToPlay: boolean
|
||||||
): MoveEval => {
|
): PositionEval => {
|
||||||
const parsedResults: MoveEval = {
|
const parsedResults: PositionEval = {
|
||||||
lines: [],
|
lines: [],
|
||||||
};
|
};
|
||||||
const tempResults: Record<string, LineEval> = {};
|
const tempResults: Record<string, LineEval> = {};
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { ceilsNumber } from "@/lib/helpers";
|
import { ceilsNumber } from "@/lib/helpers";
|
||||||
import { MoveEval } from "@/types/eval";
|
import { PositionEval } from "@/types/eval";
|
||||||
|
|
||||||
export const getPositionWinPercentage = (move: MoveEval): number => {
|
export const getPositionWinPercentage = (position: PositionEval): number => {
|
||||||
if (move.lines[0].cp !== undefined) {
|
if (position.lines[0].cp !== undefined) {
|
||||||
return getWinPercentageFromCp(move.lines[0].cp);
|
return getWinPercentageFromCp(position.lines[0].cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move.lines[0].mate !== undefined) {
|
if (position.lines[0].mate !== undefined) {
|
||||||
return getWinPercentageFromMate(move.lines[0].mate);
|
return getWinPercentageFromMate(position.lines[0].mate);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("No cp or mate in move");
|
throw new Error("No cp or mate in move");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
EvaluateGameParams,
|
EvaluateGameParams,
|
||||||
EvaluatePositionWithUpdateParams,
|
EvaluatePositionWithUpdateParams,
|
||||||
GameEval,
|
GameEval,
|
||||||
MoveEval,
|
PositionEval,
|
||||||
} from "@/types/eval";
|
} from "@/types/eval";
|
||||||
import { parseEvaluationResults } from "./helpers/parseResults";
|
import { parseEvaluationResults } from "./helpers/parseResults";
|
||||||
import { computeAccuracy } from "./helpers/accuracy";
|
import { computeAccuracy } from "./helpers/accuracy";
|
||||||
@@ -108,11 +108,11 @@ export abstract class UciEngine {
|
|||||||
await this.sendCommands(["ucinewgame", "isready"], "readyok");
|
await this.sendCommands(["ucinewgame", "isready"], "readyok");
|
||||||
this.worker.postMessage("position startpos");
|
this.worker.postMessage("position startpos");
|
||||||
|
|
||||||
const moves: MoveEval[] = [];
|
const positions: PositionEval[] = [];
|
||||||
for (const fen of fens) {
|
for (const fen of fens) {
|
||||||
const whoIsCheckmated = getWhoIsCheckmated(fen);
|
const whoIsCheckmated = getWhoIsCheckmated(fen);
|
||||||
if (whoIsCheckmated) {
|
if (whoIsCheckmated) {
|
||||||
moves.push({
|
positions.push({
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
pv: [],
|
pv: [],
|
||||||
@@ -125,19 +125,19 @@ export abstract class UciEngine {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const result = await this.evaluatePosition(fen, depth);
|
const result = await this.evaluatePosition(fen, depth);
|
||||||
moves.push(result);
|
positions.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const movesWithClassification = getMovesClassification(
|
const positionsWithClassification = getMovesClassification(
|
||||||
moves,
|
positions,
|
||||||
uciMoves,
|
uciMoves,
|
||||||
fens
|
fens
|
||||||
);
|
);
|
||||||
const accuracy = computeAccuracy(moves);
|
const accuracy = computeAccuracy(positions);
|
||||||
|
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
return {
|
return {
|
||||||
moves: movesWithClassification,
|
positions: positionsWithClassification,
|
||||||
accuracy,
|
accuracy,
|
||||||
settings: {
|
settings: {
|
||||||
engine: this.engineName,
|
engine: this.engineName,
|
||||||
@@ -148,7 +148,10 @@ export abstract class UciEngine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async evaluatePosition(fen: string, depth = 16): Promise<MoveEval> {
|
private async evaluatePosition(
|
||||||
|
fen: string,
|
||||||
|
depth = 16
|
||||||
|
): Promise<PositionEval> {
|
||||||
console.log(`Evaluating position: ${fen}`);
|
console.log(`Evaluating position: ${fen}`);
|
||||||
|
|
||||||
const lichessEval = await getLichessEval(fen, this.multiPv);
|
const lichessEval = await getLichessEval(fen, this.multiPv);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LineEval, MoveEval } from "@/types/eval";
|
import { LineEval, PositionEval } from "@/types/eval";
|
||||||
import { sortLines } from "./engine/helpers/parseResults";
|
import { sortLines } from "./engine/helpers/parseResults";
|
||||||
import {
|
import {
|
||||||
LichessError,
|
LichessError,
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
export const getLichessEval = async (
|
export const getLichessEval = async (
|
||||||
fen: string,
|
fen: string,
|
||||||
multiPv = 1
|
multiPv = 1
|
||||||
): Promise<MoveEval> => {
|
): Promise<PositionEval> => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://lichess.org/api/cloud-eval?fen=${fen}&multiPv=${multiPv}`
|
`https://lichess.org/api/cloud-eval?fen=${fen}&multiPv=${multiPv}`
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { Box, Grid, Typography } from "@mui/material";
|
import { Box, Grid, Typography } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { boardAtom, boardOrientationAtom, currentMoveAtom } from "../states";
|
import {
|
||||||
|
boardAtom,
|
||||||
|
boardOrientationAtom,
|
||||||
|
currentPositionAtom,
|
||||||
|
} from "../states";
|
||||||
import { getEvaluationBarValue } from "@/lib/chess";
|
import { getEvaluationBarValue } from "@/lib/chess";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -15,17 +19,17 @@ export default function EvaluationBar({ height }: Props) {
|
|||||||
});
|
});
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
const boardOrientation = useAtomValue(boardOrientationAtom);
|
const boardOrientation = useAtomValue(boardOrientationAtom);
|
||||||
const currentMove = useAtomValue(currentMoveAtom);
|
const position = useAtomValue(currentPositionAtom);
|
||||||
|
|
||||||
const isWhiteToPlay = board.turn() === "w";
|
const isWhiteToPlay = board.turn() === "w";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const bestLine = currentMove?.eval?.lines[0];
|
const bestLine = position?.eval?.lines[0];
|
||||||
if (!bestLine || bestLine.depth < 6) return;
|
if (!bestLine || bestLine.depth < 6) return;
|
||||||
|
|
||||||
const evalBar = getEvaluationBarValue(bestLine, isWhiteToPlay);
|
const evalBar = getEvaluationBarValue(bestLine, isWhiteToPlay);
|
||||||
setEvalBar(evalBar);
|
setEvalBar(evalBar);
|
||||||
}, [currentMove, isWhiteToPlay]);
|
}, [position, isWhiteToPlay]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { useAtomValue } from "jotai";
|
|||||||
import {
|
import {
|
||||||
boardAtom,
|
boardAtom,
|
||||||
boardOrientationAtom,
|
boardOrientationAtom,
|
||||||
currentMoveAtom,
|
currentPositionAtom,
|
||||||
showBestMoveArrowAtom,
|
showBestMoveArrowAtom,
|
||||||
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/useChessActions";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
@@ -15,6 +14,10 @@ import PlayerInfo from "./playerInfo";
|
|||||||
import EvaluationBar from "./evaluationBar";
|
import EvaluationBar from "./evaluationBar";
|
||||||
import { useScreenSize } from "@/hooks/useScreenSize";
|
import { useScreenSize } from "@/hooks/useScreenSize";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
|
import {
|
||||||
|
moveClassificationColors,
|
||||||
|
useSquareRenderer,
|
||||||
|
} from "@/hooks/useSquareRenderer";
|
||||||
|
|
||||||
export default function Board() {
|
export default function Board() {
|
||||||
const boardRef = useRef<HTMLDivElement>(null);
|
const boardRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -22,9 +25,9 @@ export default function Board() {
|
|||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
const boardOrientation = useAtomValue(boardOrientationAtom);
|
const boardOrientation = useAtomValue(boardOrientationAtom);
|
||||||
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
||||||
const showPlayerMoveArrow = useAtomValue(showPlayerMoveArrowAtom);
|
|
||||||
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
||||||
const currentMove = useAtomValue(currentMoveAtom);
|
const position = useAtomValue(currentPositionAtom);
|
||||||
|
const squareRenderer = useSquareRenderer(position);
|
||||||
|
|
||||||
const onPieceDrop = (
|
const onPieceDrop = (
|
||||||
source: Square,
|
source: Square,
|
||||||
@@ -45,9 +48,8 @@ export default function Board() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const customArrows: Arrow[] = useMemo(() => {
|
const customArrows: Arrow[] = useMemo(() => {
|
||||||
const arrows: Arrow[] = [];
|
const bestMove = position?.lastEval?.bestMove;
|
||||||
const bestMove = currentMove?.lastEval?.bestMove;
|
const moveClassification = position?.eval?.moveClassification;
|
||||||
const moveClassification = currentMove?.eval?.moveClassification;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
bestMove &&
|
bestMove &&
|
||||||
@@ -60,36 +62,11 @@ export default function Board() {
|
|||||||
moveClassificationColors[MoveClassification.Best],
|
moveClassificationColors[MoveClassification.Best],
|
||||||
] as Arrow;
|
] as Arrow;
|
||||||
|
|
||||||
arrows.push(bestMoveArrow);
|
return [bestMoveArrow];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
return [];
|
||||||
currentMove.from &&
|
}, [position, showBestMoveArrow]);
|
||||||
currentMove.to &&
|
|
||||||
showPlayerMoveArrow &&
|
|
||||||
moveClassification !== MoveClassification.Best
|
|
||||||
) {
|
|
||||||
const arrowColor = moveClassification
|
|
||||||
? moveClassificationColors[moveClassification]
|
|
||||||
: "#ffaa00";
|
|
||||||
const playerMoveArrow: Arrow = [
|
|
||||||
currentMove.from,
|
|
||||||
currentMove.to,
|
|
||||||
arrowColor,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
|
||||||
arrows.every(
|
|
||||||
(arrow) =>
|
|
||||||
arrow[0] !== playerMoveArrow[0] || arrow[1] !== playerMoveArrow[1]
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
arrows.push(playerMoveArrow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arrows;
|
|
||||||
}, [currentMove, showBestMoveArrow, showPlayerMoveArrow]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
@@ -131,6 +108,7 @@ export default function Board() {
|
|||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
||||||
}}
|
}}
|
||||||
|
customSquare={squareRenderer}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -139,13 +117,3 @@ export default function Board() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveClassificationColors: Record<MoveClassification, string> = {
|
|
||||||
[MoveClassification.Best]: "#26c2a3",
|
|
||||||
[MoveClassification.Book]: "#d5a47d",
|
|
||||||
[MoveClassification.Excellent]: "#3aab18",
|
|
||||||
[MoveClassification.Good]: "#81b64c",
|
|
||||||
[MoveClassification.Inaccuracy]: "#f7c631",
|
|
||||||
[MoveClassification.Mistake]: "#ffa459",
|
|
||||||
[MoveClassification.Blunder]: "#fa412d",
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Grid, List, Typography } from "@mui/material";
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom, engineMultiPvAtom, gameAtom } from "../states";
|
import { boardAtom, engineMultiPvAtom, gameAtom } from "../states";
|
||||||
import LineEvaluation from "./lineEvaluation";
|
import LineEvaluation from "./lineEvaluation";
|
||||||
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
import { useCurrentPosition } from "@/hooks/useCurrentPosition";
|
||||||
import { LineEval } from "@/types/eval";
|
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";
|
||||||
@@ -13,7 +13,7 @@ import Opening from "./opening";
|
|||||||
|
|
||||||
export default function ReviewPanelBody() {
|
export default function ReviewPanelBody() {
|
||||||
const linesNumber = useAtomValue(engineMultiPvAtom);
|
const linesNumber = useAtomValue(engineMultiPvAtom);
|
||||||
const move = useCurrentMove(EngineName.Stockfish16);
|
const position = useCurrentPosition(EngineName.Stockfish16);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ export default function ReviewPanelBody() {
|
|||||||
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
|
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
|
||||||
);
|
);
|
||||||
|
|
||||||
const engineLines = move?.eval?.lines?.length
|
const engineLines = position?.eval?.lines?.length
|
||||||
? move.eval.lines
|
? position.eval.lines
|
||||||
: linesSkeleton;
|
: linesSkeleton;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
|
||||||
import { Grid, Typography } from "@mui/material";
|
import { Grid, Typography } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom } from "../states";
|
import { boardAtom, currentPositionAtom } from "../states";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { moveLineUciToSan } from "@/lib/chess";
|
import { moveLineUciToSan } from "@/lib/chess";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
|
|
||||||
export default function MoveInfo() {
|
export default function MoveInfo() {
|
||||||
const move = useCurrentMove();
|
const position = useAtomValue(currentPositionAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
|
|
||||||
const bestMove = move?.lastEval?.bestMove;
|
const bestMove = position?.lastEval?.bestMove;
|
||||||
|
|
||||||
const bestMoveSan = useMemo(() => {
|
const bestMoveSan = useMemo(() => {
|
||||||
if (!bestMove) return undefined;
|
if (!bestMove) return undefined;
|
||||||
@@ -23,9 +22,9 @@ export default function MoveInfo() {
|
|||||||
|
|
||||||
if (!bestMoveSan) return null;
|
if (!bestMoveSan) return null;
|
||||||
|
|
||||||
const moveClassification = move.eval?.moveClassification;
|
const moveClassification = position.eval?.moveClassification;
|
||||||
const moveLabel = moveClassification
|
const moveLabel = moveClassification
|
||||||
? `${move.san} is ${moveClassificationLabels[moveClassification]}`
|
? `${position.lastMove?.san} is ${moveClassificationLabels[moveClassification]}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const bestMoveLabel =
|
const bestMoveLabel =
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
import { useCurrentPosition } from "@/hooks/useCurrentPosition";
|
||||||
import { Grid, Typography } from "@mui/material";
|
import { Grid, Typography } from "@mui/material";
|
||||||
|
|
||||||
export default function Opening() {
|
export default function Opening() {
|
||||||
const move = useCurrentMove();
|
const position = useCurrentPosition();
|
||||||
|
|
||||||
const opening = move?.eval?.opening;
|
const opening = position?.eval?.opening;
|
||||||
if (!opening) return null;
|
if (!opening) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { CurrentMove, GameEval } from "@/types/eval";
|
import { CurrentPosition, GameEval } from "@/types/eval";
|
||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
|
|
||||||
export const gameEvalAtom = atom<GameEval | undefined>(undefined);
|
export const gameEvalAtom = atom<GameEval | undefined>(undefined);
|
||||||
export const gameAtom = atom(new Chess());
|
export const gameAtom = atom(new Chess());
|
||||||
export const boardAtom = atom(new Chess());
|
export const boardAtom = atom(new Chess());
|
||||||
export const currentMoveAtom = atom<CurrentMove>({});
|
export const currentPositionAtom = atom<CurrentPosition>({});
|
||||||
|
|
||||||
export const boardOrientationAtom = atom(true);
|
export const boardOrientationAtom = atom(true);
|
||||||
export const showBestMoveArrowAtom = atom(true);
|
export const showBestMoveArrowAtom = atom(true);
|
||||||
export const showPlayerMoveArrowAtom = atom(true);
|
export const showPlayerMoveIconAtom = atom(true);
|
||||||
|
|
||||||
export const engineDepthAtom = atom(16);
|
export const engineDepthAtom = atom(16);
|
||||||
export const engineMultiPvAtom = atom(3);
|
export const engineMultiPvAtom = atom(3);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Checkbox, FormControlLabel, Grid } from "@mui/material";
|
import { Checkbox, FormControlLabel, Grid } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
showBestMoveArrowAtom,
|
showBestMoveArrowAtom,
|
||||||
showPlayerMoveArrowAtom,
|
showPlayerMoveIconAtom,
|
||||||
} from "../analysis/states";
|
} from "../analysis/states";
|
||||||
import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
|
import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
|
||||||
|
|
||||||
@@ -10,9 +10,9 @@ export default function ArrowOptions() {
|
|||||||
"show-arrow-best-move",
|
"show-arrow-best-move",
|
||||||
showBestMoveArrowAtom
|
showBestMoveArrowAtom
|
||||||
);
|
);
|
||||||
const [showPlayerMove, setShowPlayerMove] = useAtomLocalStorage(
|
const [showPlayerMoveIcon, setShowPlayerMoveIcon] = useAtomLocalStorage(
|
||||||
"show-arrow-player-move",
|
"show-icon-player-move",
|
||||||
showPlayerMoveArrowAtom
|
showPlayerMoveIconAtom
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,11 +37,11 @@ export default function ArrowOptions() {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={showPlayerMove}
|
checked={showPlayerMoveIcon}
|
||||||
onChange={(_, checked) => setShowPlayerMove(checked)}
|
onChange={(_, checked) => setShowPlayerMoveIcon(checked)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Show played move arrow"
|
label="Show played move icon"
|
||||||
sx={{ marginX: 0 }}
|
sx={{ marginX: 0 }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Move } from "chess.js";
|
import { Move } from "chess.js";
|
||||||
import { EngineName, MoveClassification } from "./enums";
|
import { EngineName, MoveClassification } from "./enums";
|
||||||
|
|
||||||
export interface MoveEval {
|
export interface PositionEval {
|
||||||
bestMove?: string;
|
bestMove?: string;
|
||||||
moveClassification?: MoveClassification;
|
moveClassification?: MoveClassification;
|
||||||
opening?: string;
|
opening?: string;
|
||||||
@@ -29,7 +29,7 @@ export interface EngineSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GameEval {
|
export interface GameEval {
|
||||||
moves: MoveEval[];
|
positions: PositionEval[];
|
||||||
accuracy: Accuracy;
|
accuracy: Accuracy;
|
||||||
settings: EngineSettings;
|
settings: EngineSettings;
|
||||||
}
|
}
|
||||||
@@ -38,13 +38,14 @@ export interface EvaluatePositionWithUpdateParams {
|
|||||||
fen: string;
|
fen: string;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
multiPv?: number;
|
multiPv?: number;
|
||||||
setPartialEval: (moveEval: MoveEval) => void;
|
setPartialEval: (positionEval: PositionEval) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurrentMove = Partial<Move> & {
|
export interface CurrentPosition {
|
||||||
eval?: MoveEval;
|
lastMove?: Move;
|
||||||
lastEval?: MoveEval;
|
eval?: PositionEval;
|
||||||
};
|
lastEval?: PositionEval;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EvaluateGameParams {
|
export interface EvaluateGameParams {
|
||||||
fens: string[];
|
fens: string[];
|
||||||
|
|||||||