feat : add live position evaluation
This commit is contained in:
@@ -1,8 +1,16 @@
|
|||||||
import { boardAtom, gameAtom, gameEvalAtom } from "@/sections/analysis/states";
|
import {
|
||||||
|
boardAtom,
|
||||||
|
engineDepthAtom,
|
||||||
|
engineMultiPvAtom,
|
||||||
|
gameAtom,
|
||||||
|
gameEvalAtom,
|
||||||
|
} from "@/sections/analysis/states";
|
||||||
import { MoveEval } from "@/types/eval";
|
import { MoveEval } from "@/types/eval";
|
||||||
import { Move } from "chess.js";
|
import { Move } from "chess.js";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useMemo } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useEngine } from "./useEngine";
|
||||||
|
import { EngineName } from "@/types/enums";
|
||||||
|
|
||||||
export type CurrentMove = Partial<Move> & {
|
export type CurrentMove = Partial<Move> & {
|
||||||
eval?: MoveEval;
|
eval?: MoveEval;
|
||||||
@@ -10,35 +18,50 @@ export type CurrentMove = Partial<Move> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useCurrentMove = () => {
|
export const useCurrentMove = () => {
|
||||||
|
const [currentMove, setCurrentMove] = useState<CurrentMove>({});
|
||||||
|
const engine = useEngine(EngineName.Stockfish16);
|
||||||
const gameEval = useAtomValue(gameEvalAtom);
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
|
const depth = useAtomValue(engineDepthAtom);
|
||||||
|
const multiPv = useAtomValue(engineMultiPvAtom);
|
||||||
|
|
||||||
const currentMove: CurrentMove = useMemo(() => {
|
useEffect(() => {
|
||||||
const move = {
|
const move: CurrentMove = {
|
||||||
...board.history({ verbose: true }).at(-1),
|
...board.history({ verbose: true }).at(-1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!gameEval) return move;
|
if (gameEval) {
|
||||||
|
const boardHistory = board.history();
|
||||||
|
const gameHistory = game.history();
|
||||||
|
|
||||||
const boardHistory = board.history();
|
if (
|
||||||
const gameHistory = game.history();
|
boardHistory.length <= gameHistory.length &&
|
||||||
|
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
||||||
|
) {
|
||||||
|
const evalIndex = board.history().length;
|
||||||
|
|
||||||
if (
|
move.eval = gameEval.moves[evalIndex];
|
||||||
boardHistory.length <= gameHistory.length &&
|
move.lastEval =
|
||||||
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
evalIndex > 0 ? gameEval.moves[evalIndex - 1] : undefined;
|
||||||
) {
|
}
|
||||||
const evalIndex = board.history().length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...move,
|
|
||||||
eval: gameEval.moves[evalIndex],
|
|
||||||
lastEval: evalIndex > 0 ? gameEval.moves[evalIndex - 1] : undefined,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return move;
|
if (!move.eval && engine?.isReady()) {
|
||||||
}, [gameEval, board, game]);
|
const setPartialEval = (moveEval: MoveEval) => {
|
||||||
|
setCurrentMove({ ...move, eval: moveEval });
|
||||||
|
};
|
||||||
|
|
||||||
|
engine.evaluatePositionWithUpdate({
|
||||||
|
fen: board.fen(),
|
||||||
|
depth,
|
||||||
|
multiPv,
|
||||||
|
setPartialEval,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentMove(move);
|
||||||
|
}, [gameEval, board, game, engine, depth, multiPv]);
|
||||||
|
|
||||||
return currentMove;
|
return currentMove;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,3 +57,23 @@ export const getGameToSave = (game: Chess, board: Chess): Chess => {
|
|||||||
|
|
||||||
return board;
|
return board;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const moveLineUciToSan = (
|
||||||
|
fen: string
|
||||||
|
): ((moveUci: string) => string) => {
|
||||||
|
const game = new Chess(fen);
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
return move.san;
|
||||||
|
} catch (e) {
|
||||||
|
return moveUci;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { EngineName } from "@/types/enums";
|
import { EngineName } from "@/types/enums";
|
||||||
import { GameEval, LineEval, MoveEval } from "@/types/eval";
|
import {
|
||||||
|
EvaluatePositionWithUpdateParams,
|
||||||
|
GameEval,
|
||||||
|
LineEval,
|
||||||
|
MoveEval,
|
||||||
|
} from "@/types/eval";
|
||||||
|
|
||||||
export abstract class UciEngine {
|
export abstract class UciEngine {
|
||||||
private worker: Worker;
|
private worker: Worker;
|
||||||
@@ -18,15 +23,15 @@ export abstract class UciEngine {
|
|||||||
|
|
||||||
public async init(): Promise<void> {
|
public async init(): Promise<void> {
|
||||||
await this.sendCommands(["uci"], "uciok");
|
await this.sendCommands(["uci"], "uciok");
|
||||||
await this.setMultiPv(this.multiPv, false);
|
await this.setMultiPv(this.multiPv, true);
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
console.log(`${this.engineName} initialized`);
|
console.log(`${this.engineName} initialized`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setMultiPv(multiPv: number, checkIsReady = true) {
|
private async setMultiPv(multiPv: number, initCase = false) {
|
||||||
if (multiPv === this.multiPv) return;
|
if (!initCase) {
|
||||||
|
if (multiPv === this.multiPv) return;
|
||||||
|
|
||||||
if (checkIsReady) {
|
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,15 +64,23 @@ export abstract class UciEngine {
|
|||||||
return this.ready;
|
return this.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async stopSearch(): Promise<void> {
|
||||||
|
await this.sendCommands(["stop", "isready"], "readyok");
|
||||||
|
}
|
||||||
|
|
||||||
private async sendCommands(
|
private async sendCommands(
|
||||||
commands: string[],
|
commands: string[],
|
||||||
finalMessage: string
|
finalMessage: string,
|
||||||
|
onNewMessage?: (messages: string[]) => void
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const messages: string[] = [];
|
const messages: string[] = [];
|
||||||
|
|
||||||
this.worker.onmessage = (event) => {
|
this.worker.onmessage = (event) => {
|
||||||
const messageData: string = event.data;
|
const messageData: string = event.data;
|
||||||
messages.push(messageData);
|
messages.push(messageData);
|
||||||
|
onNewMessage?.(messages);
|
||||||
|
|
||||||
if (messageData.startsWith(finalMessage)) {
|
if (messageData.startsWith(finalMessage)) {
|
||||||
resolve(messages);
|
resolve(messages);
|
||||||
}
|
}
|
||||||
@@ -85,6 +98,7 @@ export abstract class UciEngine {
|
|||||||
multiPv = this.multiPv
|
multiPv = this.multiPv
|
||||||
): Promise<GameEval> {
|
): Promise<GameEval> {
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
|
await this.setMultiPv(multiPv);
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
|
|
||||||
await this.sendCommands(["ucinewgame", "isready"], "readyok");
|
await this.sendCommands(["ucinewgame", "isready"], "readyok");
|
||||||
@@ -92,7 +106,7 @@ export abstract class UciEngine {
|
|||||||
|
|
||||||
const moves: MoveEval[] = [];
|
const moves: MoveEval[] = [];
|
||||||
for (const fen of fens) {
|
for (const fen of fens) {
|
||||||
const result = await this.evaluatePosition(fen, depth, multiPv, false);
|
const result = await this.evaluatePosition(fen, depth);
|
||||||
moves.push(result);
|
moves.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,44 +123,46 @@ export abstract class UciEngine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async evaluatePosition(
|
private async evaluatePosition(fen: string, depth = 16): Promise<MoveEval> {
|
||||||
fen: string,
|
|
||||||
depth = 16,
|
|
||||||
multiPv = this.multiPv,
|
|
||||||
checkIsReady = true
|
|
||||||
): Promise<MoveEval> {
|
|
||||||
if (checkIsReady) {
|
|
||||||
this.throwErrorIfNotReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.setMultiPv(multiPv, checkIsReady);
|
|
||||||
|
|
||||||
console.log(`Evaluating position: ${fen}`);
|
console.log(`Evaluating position: ${fen}`);
|
||||||
|
|
||||||
const results = await this.sendCommands(
|
const results = await this.sendCommands(
|
||||||
[`position fen ${fen}`, `go depth ${depth}`],
|
[`position fen ${fen}`, `go depth ${depth}`],
|
||||||
"bestmove"
|
"bestmove"
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsedResults = this.parseResults(results);
|
const whiteToPlay = fen.split(" ")[1] === "w";
|
||||||
|
|
||||||
|
return this.parseResults(results, whiteToPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async evaluatePositionWithUpdate({
|
||||||
|
fen,
|
||||||
|
depth = 16,
|
||||||
|
multiPv = this.multiPv,
|
||||||
|
setPartialEval,
|
||||||
|
}: EvaluatePositionWithUpdateParams): Promise<void> {
|
||||||
|
this.throwErrorIfNotReady();
|
||||||
|
|
||||||
|
await this.stopSearch();
|
||||||
|
await this.setMultiPv(multiPv);
|
||||||
|
|
||||||
const whiteToPlay = fen.split(" ")[1] === "w";
|
const whiteToPlay = fen.split(" ")[1] === "w";
|
||||||
|
|
||||||
if (!whiteToPlay) {
|
const onNewMessage = (messages: string[]) => {
|
||||||
const lines = parsedResults.lines.map((line) => ({
|
const parsedResults = this.parseResults(messages, whiteToPlay);
|
||||||
...line,
|
setPartialEval(parsedResults);
|
||||||
cp: line.cp ? -line.cp : line.cp,
|
};
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
console.log(`Evaluating position: ${fen}`);
|
||||||
...parsedResults,
|
await this.sendCommands(
|
||||||
lines,
|
[`position fen ${fen}`, `go depth ${depth}`],
|
||||||
};
|
"bestmove",
|
||||||
}
|
onNewMessage
|
||||||
|
);
|
||||||
return parsedResults;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseResults(results: string[]): MoveEval {
|
private parseResults(results: string[], whiteToPlay: boolean): MoveEval {
|
||||||
const parsedResults: MoveEval = {
|
const parsedResults: MoveEval = {
|
||||||
bestMove: "",
|
bestMove: "",
|
||||||
lines: [],
|
lines: [],
|
||||||
@@ -189,6 +205,13 @@ export abstract class UciEngine {
|
|||||||
|
|
||||||
parsedResults.lines = Object.values(tempResults).sort(this.sortLines);
|
parsedResults.lines = Object.values(tempResults).sort(this.sortLines);
|
||||||
|
|
||||||
|
if (!whiteToPlay) {
|
||||||
|
parsedResults.lines = parsedResults.lines.map((line) => ({
|
||||||
|
...line,
|
||||||
|
cp: line.cp ? -line.cp : line.cp,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return parsedResults;
|
return parsedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { LineEval } from "@/types/eval";
|
import { LineEval } from "@/types/eval";
|
||||||
import { ListItem, ListItemText, Typography } from "@mui/material";
|
import { ListItem, Skeleton, Typography } from "@mui/material";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { boardAtom } from "./states";
|
||||||
|
import { moveLineUciToSan } from "@/lib/chess";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
line: LineEval;
|
line: LineEval;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LineEvaluation({ line }: Props) {
|
export default function LineEvaluation({ line }: Props) {
|
||||||
|
const board = useAtomValue(boardAtom);
|
||||||
const lineLabel =
|
const lineLabel =
|
||||||
line.cp !== undefined
|
line.cp !== undefined
|
||||||
? `${line.cp / 100}`
|
? `${line.cp / 100}`
|
||||||
@@ -13,10 +17,32 @@ export default function LineEvaluation({ line }: Props) {
|
|||||||
? `Mate in ${Math.abs(line.mate)}`
|
? `Mate in ${Math.abs(line.mate)}`
|
||||||
: "?";
|
: "?";
|
||||||
|
|
||||||
|
const showSkeleton = line.depth === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemText primary={lineLabel} sx={{ marginRight: 2 }} />
|
<Typography marginRight={2} marginY={0.5}>
|
||||||
<Typography>{line.pv.slice(0, 7).join(", ")}</Typography>
|
{showSkeleton ? (
|
||||||
|
<Skeleton
|
||||||
|
width={"2em"}
|
||||||
|
variant="rounded"
|
||||||
|
animation="wave"
|
||||||
|
sx={{ color: "transparent" }}
|
||||||
|
>
|
||||||
|
placeholder
|
||||||
|
</Skeleton>
|
||||||
|
) : (
|
||||||
|
lineLabel
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography>
|
||||||
|
{showSkeleton ? (
|
||||||
|
<Skeleton width={"30em"} variant="rounded" animation="wave" />
|
||||||
|
) : (
|
||||||
|
line.pv.slice(0, 10).map(moveLineUciToSan(board.fen())).join(", ")
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { Divider, Grid, List, Typography } from "@mui/material";
|
import { Divider, Grid, List, Typography } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom, gameAtom } from "./states";
|
import { boardAtom, engineMultiPvAtom, gameAtom } from "./states";
|
||||||
import LineEvaluation from "./lineEvaluation";
|
import LineEvaluation from "./lineEvaluation";
|
||||||
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
import { useCurrentMove } from "@/hooks/useCurrentMove";
|
||||||
|
import { LineEval } from "@/types/eval";
|
||||||
|
|
||||||
export default function ReviewPanelBody() {
|
export default function ReviewPanelBody() {
|
||||||
|
const linesNumber = useAtomValue(engineMultiPvAtom);
|
||||||
const move = useCurrentMove();
|
const move = useCurrentMove();
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
@@ -17,6 +19,14 @@ export default function ReviewPanelBody() {
|
|||||||
const isGameOver =
|
const isGameOver =
|
||||||
gameHistory.length > 0 && boardHistory.join() === gameHistory.join();
|
gameHistory.length > 0 && boardHistory.join() === gameHistory.join();
|
||||||
|
|
||||||
|
const linesSkeleton: LineEval[] = Array.from({ length: linesNumber }).map(
|
||||||
|
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
|
||||||
|
);
|
||||||
|
|
||||||
|
const engineLines = move?.eval?.lines.length
|
||||||
|
? move.eval.lines
|
||||||
|
: linesSkeleton;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider sx={{ width: "90%", marginY: 3 }} />
|
<Divider sx={{ width: "90%", marginY: 3 }} />
|
||||||
@@ -57,8 +67,8 @@ export default function ReviewPanelBody() {
|
|||||||
|
|
||||||
<Grid item container xs={12} justifyContent="center" alignItems="center">
|
<Grid item container xs={12} justifyContent="center" alignItems="center">
|
||||||
<List>
|
<List>
|
||||||
{move?.eval?.lines.map((line) => (
|
{engineLines.map((line) => (
|
||||||
<LineEvaluation key={line.pv[0]} line={line} />
|
<LineEvaluation key={line.multiPv} line={line} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
47
src/sections/analysis/reviewPanelToolbar/arrowOptions.tsx
Normal file
47
src/sections/analysis/reviewPanelToolbar/arrowOptions.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Checkbox, FormControlLabel, Grid } from "@mui/material";
|
||||||
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
|
import {
|
||||||
|
gameEvalAtom,
|
||||||
|
showBestMoveArrowAtom,
|
||||||
|
showPlayerMoveArrowAtom,
|
||||||
|
} from "../states";
|
||||||
|
|
||||||
|
export default function ArrowOptions() {
|
||||||
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
|
const [showBestMove, setShowBestMove] = useAtom(showBestMoveArrowAtom);
|
||||||
|
const [showPlayerMove, setShowPlayerMove] = useAtom(showPlayerMoveArrowAtom);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
item
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
xs={12}
|
||||||
|
marginY={3}
|
||||||
|
gap={3}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={showBestMove}
|
||||||
|
onChange={(_, checked) => setShowBestMove(checked)}
|
||||||
|
disabled={!gameEval}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Show best move green arrow"
|
||||||
|
sx={{ marginX: 0 }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={showPlayerMove}
|
||||||
|
onChange={(_, checked) => setShowPlayerMove(checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Show player move yellow arrow"
|
||||||
|
sx={{ marginX: 0 }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,27 +1,15 @@
|
|||||||
import {
|
import { Divider, Grid, IconButton, Tooltip } from "@mui/material";
|
||||||
Checkbox,
|
|
||||||
Divider,
|
|
||||||
FormControlLabel,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import {
|
import { boardAtom } from "../states";
|
||||||
boardAtom,
|
|
||||||
showBestMoveArrowAtom,
|
|
||||||
showPlayerMoveArrowAtom,
|
|
||||||
} from "../states";
|
|
||||||
import { useChessActions } from "@/hooks/useChess";
|
import { useChessActions } from "@/hooks/useChess";
|
||||||
import FlipBoardButton from "./flipBoardButton";
|
import FlipBoardButton from "./flipBoardButton";
|
||||||
import NextMoveButton from "./nextMoveButton";
|
import NextMoveButton from "./nextMoveButton";
|
||||||
import GoToLastPositionButton from "./goToLastPositionButton";
|
import GoToLastPositionButton from "./goToLastPositionButton";
|
||||||
import SaveButton from "./saveButton";
|
import SaveButton from "./saveButton";
|
||||||
|
import ArrowOptions from "./arrowOptions";
|
||||||
|
|
||||||
export default function ReviewPanelToolBar() {
|
export default function ReviewPanelToolBar() {
|
||||||
const [showBestMove, setShowBestMove] = useAtom(showBestMoveArrowAtom);
|
|
||||||
const [showPlayerMove, setShowPlayerMove] = useAtom(showPlayerMoveArrowAtom);
|
|
||||||
const board = useAtomValue(boardAtom);
|
const board = useAtomValue(boardAtom);
|
||||||
const boardActions = useChessActions(boardAtom);
|
const boardActions = useChessActions(boardAtom);
|
||||||
|
|
||||||
@@ -63,36 +51,7 @@ export default function ReviewPanelToolBar() {
|
|||||||
<SaveButton />
|
<SaveButton />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<ArrowOptions />
|
||||||
container
|
|
||||||
item
|
|
||||||
justifyContent="space-evenly"
|
|
||||||
alignItems="center"
|
|
||||||
xs={12}
|
|
||||||
marginY={3}
|
|
||||||
gap={3}
|
|
||||||
>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={showBestMove}
|
|
||||||
onChange={(_, checked) => setShowBestMove(checked)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Show best move green arrow"
|
|
||||||
sx={{ marginX: 0 }}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={showPlayerMove}
|
|
||||||
onChange={(_, checked) => setShowPlayerMove(checked)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Show player move yellow arrow"
|
|
||||||
sx={{ marginX: 0 }}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,10 @@ export interface GameEval {
|
|||||||
accuracy: Accuracy;
|
accuracy: Accuracy;
|
||||||
settings: EngineSettings;
|
settings: EngineSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EvaluatePositionWithUpdateParams {
|
||||||
|
fen: string;
|
||||||
|
depth?: number;
|
||||||
|
multiPv?: number;
|
||||||
|
setPartialEval: (moveEval: MoveEval) => void;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user