feat : add piece drag hint
This commit is contained in:
@@ -90,7 +90,7 @@ export abstract class UciEngine {
|
|||||||
return this.ready;
|
return this.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async stopSearch(): Promise<void> {
|
public async stopSearch(): Promise<void> {
|
||||||
await this.sendCommands(["stop", "isready"], "readyok");
|
await this.sendCommands(["stop", "isready"], "readyok");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ export abstract class UciEngine {
|
|||||||
fen: string,
|
fen: string,
|
||||||
skillLevel: number,
|
skillLevel: number,
|
||||||
depth = 16
|
depth = 16
|
||||||
): Promise<string> {
|
): Promise<string | undefined> {
|
||||||
this.throwErrorIfNotReady();
|
this.throwErrorIfNotReady();
|
||||||
await this.setSkillLevel(skillLevel);
|
await this.setSkillLevel(skillLevel);
|
||||||
|
|
||||||
@@ -260,6 +260,6 @@ export abstract class UciEngine {
|
|||||||
throw new Error("No move found");
|
throw new Error("No move found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return move;
|
return move === "(none)" ? undefined : move;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
boardOrientationAtom,
|
boardOrientationAtom,
|
||||||
clickedSquaresAtom,
|
clickedSquaresAtom,
|
||||||
currentPositionAtom,
|
currentPositionAtom,
|
||||||
|
playableSquaresAtom,
|
||||||
showBestMoveArrowAtom,
|
showBestMoveArrowAtom,
|
||||||
} from "../states";
|
} from "../states";
|
||||||
import { Arrow, Square } from "react-chessboard/dist/chessboard/types";
|
import { Arrow, Square } from "react-chessboard/dist/chessboard/types";
|
||||||
@@ -26,6 +27,7 @@ export default function Board() {
|
|||||||
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
const { makeMove: makeBoardMove } = useChessActions(boardAtom);
|
||||||
const position = useAtomValue(currentPositionAtom);
|
const position = useAtomValue(currentPositionAtom);
|
||||||
const setClickedSquares = useSetAtom(clickedSquaresAtom);
|
const setClickedSquares = useSetAtom(clickedSquaresAtom);
|
||||||
|
const setPlayableSquares = useSetAtom(playableSquaresAtom);
|
||||||
|
|
||||||
const boardFen = board.fen();
|
const boardFen = board.fen();
|
||||||
|
|
||||||
@@ -51,6 +53,27 @@ export default function Board() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSquareLeftClick = () => {
|
||||||
|
setClickedSquares([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSquareRightClick = (square: Square) => {
|
||||||
|
setClickedSquares((prev) =>
|
||||||
|
prev.includes(square)
|
||||||
|
? prev.filter((s) => s !== square)
|
||||||
|
: [...prev, square]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePieceDragBegin = (_: string, square: Square) => {
|
||||||
|
const moves = board.moves({ square, verbose: true });
|
||||||
|
setPlayableSquares(moves.map((m) => m.to));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePieceDragEnd = () => {
|
||||||
|
setPlayableSquares([]);
|
||||||
|
};
|
||||||
|
|
||||||
const customArrows: Arrow[] = useMemo(() => {
|
const customArrows: Arrow[] = useMemo(() => {
|
||||||
const bestMove = position?.lastEval?.bestMove;
|
const bestMove = position?.lastEval?.bestMove;
|
||||||
const moveClassification = position?.eval?.moveClassification;
|
const moveClassification = position?.eval?.moveClassification;
|
||||||
@@ -113,6 +136,10 @@ export default function Board() {
|
|||||||
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
||||||
}}
|
}}
|
||||||
customSquare={SquareRenderer}
|
customSquare={SquareRenderer}
|
||||||
|
onSquareClick={handleSquareLeftClick}
|
||||||
|
onSquareRightClick={handleSquareRightClick}
|
||||||
|
onPieceDragBegin={handlePieceDragBegin}
|
||||||
|
onPieceDragEnd={handlePieceDragEnd}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
clickedSquaresAtom,
|
clickedSquaresAtom,
|
||||||
currentPositionAtom,
|
currentPositionAtom,
|
||||||
|
playableSquaresAtom,
|
||||||
showPlayerMoveIconAtom,
|
showPlayerMoveIconAtom,
|
||||||
} from "../states";
|
} from "../states";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
import { atom, useAtom, useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { CSSProperties, MouseEventHandler, forwardRef } from "react";
|
import { CSSProperties, forwardRef } from "react";
|
||||||
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
|
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
|
||||||
|
|
||||||
const rightClickEventSquareAtom = atom<string | null>(null);
|
|
||||||
|
|
||||||
const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { children, square, style } = props;
|
const { children, square, style } = props;
|
||||||
const showPlayerMoveIcon = useAtomValue(showPlayerMoveIconAtom);
|
const showPlayerMoveIcon = useAtomValue(showPlayerMoveIconAtom);
|
||||||
const position = useAtomValue(currentPositionAtom);
|
const position = useAtomValue(currentPositionAtom);
|
||||||
const [clickedSquares, setClickedSquares] = useAtom(clickedSquaresAtom);
|
const clickedSquares = useAtomValue(clickedSquaresAtom);
|
||||||
const [rightClickEventSquare, setRightClickEventSquare] = useAtom(
|
const playableSquares = useAtomValue(playableSquaresAtom);
|
||||||
rightClickEventSquareAtom
|
|
||||||
);
|
|
||||||
|
|
||||||
const fromSquare = position.lastMove?.from;
|
const fromSquare = position.lastMove?.from;
|
||||||
const toSquare = position.lastMove?.to;
|
const toSquare = position.lastMove?.to;
|
||||||
const moveClassification = position?.eval?.moveClassification;
|
const moveClassification = position?.eval?.moveClassification;
|
||||||
|
|
||||||
const customSquareStyle: CSSProperties | undefined =
|
const highlightSquareStyle: CSSProperties | undefined =
|
||||||
clickedSquares.includes(square)
|
clickedSquares.includes(square)
|
||||||
? {
|
? {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -46,37 +43,25 @@ const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const handleSquareLeftClick: MouseEventHandler<HTMLDivElement> = () => {
|
const playableSquareStyle: CSSProperties | undefined =
|
||||||
setClickedSquares([]);
|
playableSquares.includes(square)
|
||||||
};
|
? {
|
||||||
|
position: "absolute",
|
||||||
const handleSquareRightClick: MouseEventHandler<HTMLDivElement> = (
|
width: "100%",
|
||||||
event
|
height: "100%",
|
||||||
) => {
|
backgroundColor: "rgba(0,0,0,.14)",
|
||||||
if (event.button !== 2) return;
|
padding: "35%",
|
||||||
|
backgroundClip: "content-box",
|
||||||
if (rightClickEventSquare !== square) {
|
borderRadius: "50%",
|
||||||
setRightClickEventSquare(null);
|
boxSizing: "border-box",
|
||||||
return;
|
}
|
||||||
}
|
: undefined;
|
||||||
|
|
||||||
setClickedSquares((prev) =>
|
|
||||||
prev.includes(square)
|
|
||||||
? prev.filter((s) => s !== square)
|
|
||||||
: [...prev, square]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={ref} style={{ ...style, position: "relative" }}>
|
||||||
ref={ref}
|
|
||||||
style={{ ...style, position: "relative" }}
|
|
||||||
onClick={handleSquareLeftClick}
|
|
||||||
onMouseDown={(e) => e.button === 2 && setRightClickEventSquare(square)}
|
|
||||||
onMouseUp={handleSquareRightClick}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
{customSquareStyle && <div style={customSquareStyle} />}
|
{highlightSquareStyle && <div style={highlightSquareStyle} />}
|
||||||
|
{playableSquareStyle && <div style={playableSquareStyle} />}
|
||||||
{moveClassification && showPlayerMoveIcon && square === toSquare && (
|
{moveClassification && showPlayerMoveIcon && square === toSquare && (
|
||||||
<Image
|
<Image
|
||||||
src={`/icons/${moveClassification}.png`}
|
src={`/icons/${moveClassification}.png`}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const boardOrientationAtom = atom(true);
|
|||||||
export const showBestMoveArrowAtom = atom(true);
|
export const showBestMoveArrowAtom = atom(true);
|
||||||
export const showPlayerMoveIconAtom = atom(true);
|
export const showPlayerMoveIconAtom = atom(true);
|
||||||
export const clickedSquaresAtom = atom<string[]>([]);
|
export const clickedSquaresAtom = atom<string[]>([]);
|
||||||
|
export const playableSquaresAtom = atom<string[]>([]);
|
||||||
|
|
||||||
export const engineDepthAtom = atom(16);
|
export const engineDepthAtom = atom(16);
|
||||||
export const engineMultiPvAtom = atom(3);
|
export const engineMultiPvAtom = atom(3);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
clickedSquaresAtom,
|
clickedSquaresAtom,
|
||||||
engineSkillLevelAtom,
|
engineSkillLevelAtom,
|
||||||
gameAtom,
|
gameAtom,
|
||||||
|
playableSquaresAtom,
|
||||||
playerColorAtom,
|
playerColorAtom,
|
||||||
} from "../states";
|
} from "../states";
|
||||||
import { Square } from "react-chessboard/dist/chessboard/types";
|
import { Square } from "react-chessboard/dist/chessboard/types";
|
||||||
@@ -24,23 +25,30 @@ export default function Board() {
|
|||||||
const playerColor = useAtomValue(playerColorAtom);
|
const playerColor = useAtomValue(playerColorAtom);
|
||||||
const { makeMove: makeBoardMove } = useChessActions(gameAtom);
|
const { makeMove: makeBoardMove } = useChessActions(gameAtom);
|
||||||
const setClickedSquares = useSetAtom(clickedSquaresAtom);
|
const setClickedSquares = useSetAtom(clickedSquaresAtom);
|
||||||
|
const setPlayableSquares = useSetAtom(playableSquaresAtom);
|
||||||
const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
|
const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
|
||||||
const engine = useEngine(EngineName.Stockfish16);
|
const engine = useEngine(EngineName.Stockfish16);
|
||||||
|
|
||||||
const gameFen = game.fen();
|
const gameFen = game.fen();
|
||||||
const turn = game.turn();
|
const isGameFinished = game.isGameOver();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const playEngineMove = async () => {
|
const playEngineMove = async () => {
|
||||||
if (!engine?.isReady() || turn === playerColor) return;
|
if (!engine?.isReady() || game.turn() === playerColor || isGameFinished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const move = await engine.getEngineNextMove(
|
const move = await engine.getEngineNextMove(
|
||||||
gameFen,
|
gameFen,
|
||||||
engineSkillLevel - 1
|
engineSkillLevel - 1
|
||||||
);
|
);
|
||||||
makeBoardMove(uciMoveParams(move));
|
if (move) makeBoardMove(uciMoveParams(move));
|
||||||
};
|
};
|
||||||
playEngineMove();
|
playEngineMove();
|
||||||
}, [engine, turn, engine, playerColor, engineSkillLevel]);
|
|
||||||
|
return () => {
|
||||||
|
engine?.stopSearch();
|
||||||
|
};
|
||||||
|
}, [gameFen, engine]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setClickedSquares([]);
|
setClickedSquares([]);
|
||||||
@@ -70,6 +78,27 @@ export default function Board() {
|
|||||||
return playerColor === piece[0];
|
return playerColor === piece[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSquareLeftClick = () => {
|
||||||
|
setClickedSquares([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSquareRightClick = (square: Square) => {
|
||||||
|
setClickedSquares((prev) =>
|
||||||
|
prev.includes(square)
|
||||||
|
? prev.filter((s) => s !== square)
|
||||||
|
: [...prev, square]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePieceDragBegin = (_: string, square: Square) => {
|
||||||
|
const moves = game.moves({ square, verbose: true });
|
||||||
|
setPlayableSquares(moves.map((m) => m.to));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePieceDragEnd = () => {
|
||||||
|
setPlayableSquares([]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
@@ -103,6 +132,10 @@ export default function Board() {
|
|||||||
}}
|
}}
|
||||||
isDraggablePiece={isPieceDraggable}
|
isDraggablePiece={isPieceDraggable}
|
||||||
customSquare={SquareRenderer}
|
customSquare={SquareRenderer}
|
||||||
|
onSquareClick={handleSquareLeftClick}
|
||||||
|
onSquareRightClick={handleSquareRightClick}
|
||||||
|
onPieceDragBegin={handlePieceDragBegin}
|
||||||
|
onPieceDragEnd={handlePieceDragEnd}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
import { clickedSquaresAtom, gameAtom } from "../states";
|
import { clickedSquaresAtom, gameAtom, playableSquaresAtom } from "../states";
|
||||||
import { atom, useAtom, useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { CSSProperties, MouseEventHandler, forwardRef } from "react";
|
import { CSSProperties, forwardRef } from "react";
|
||||||
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
|
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
|
||||||
|
|
||||||
const rightClickEventSquareAtom = atom<string | null>(null);
|
|
||||||
|
|
||||||
const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { children, square, style } = props;
|
const { children, square, style } = props;
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const [clickedSquares, setClickedSquares] = useAtom(clickedSquaresAtom);
|
const clickedSquares = useAtomValue(clickedSquaresAtom);
|
||||||
const [rightClickEventSquare, setRightClickEventSquare] = useAtom(
|
const playableSquares = useAtomValue(playableSquaresAtom);
|
||||||
rightClickEventSquareAtom
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastMove = game.history({ verbose: true }).at(-1);
|
const lastMove = game.history({ verbose: true }).at(-1);
|
||||||
const fromSquare = lastMove?.from;
|
const fromSquare = lastMove?.from;
|
||||||
const toSquare = lastMove?.to;
|
const toSquare = lastMove?.to;
|
||||||
|
|
||||||
const customSquareStyle: CSSProperties | undefined =
|
const highlightSquareStyle: CSSProperties | undefined =
|
||||||
clickedSquares.includes(square)
|
clickedSquares.includes(square)
|
||||||
? {
|
? {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -37,37 +33,25 @@ const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const handleSquareLeftClick: MouseEventHandler<HTMLDivElement> = () => {
|
const playableSquareStyle: CSSProperties | undefined =
|
||||||
setClickedSquares([]);
|
playableSquares.includes(square)
|
||||||
};
|
? {
|
||||||
|
position: "absolute",
|
||||||
const handleSquareRightClick: MouseEventHandler<HTMLDivElement> = (
|
width: "100%",
|
||||||
event
|
height: "100%",
|
||||||
) => {
|
backgroundColor: "rgba(0,0,0,.14)",
|
||||||
if (event.button !== 2) return;
|
padding: "35%",
|
||||||
|
backgroundClip: "content-box",
|
||||||
if (rightClickEventSquare !== square) {
|
borderRadius: "50%",
|
||||||
setRightClickEventSquare(null);
|
boxSizing: "border-box",
|
||||||
return;
|
}
|
||||||
}
|
: undefined;
|
||||||
|
|
||||||
setClickedSquares((prev) =>
|
|
||||||
prev.includes(square)
|
|
||||||
? prev.filter((s) => s !== square)
|
|
||||||
: [...prev, square]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={ref} style={{ ...style, position: "relative" }}>
|
||||||
ref={ref}
|
|
||||||
style={{ ...style, position: "relative" }}
|
|
||||||
onClick={handleSquareLeftClick}
|
|
||||||
onMouseDown={(e) => e.button === 2 && setRightClickEventSquare(square)}
|
|
||||||
onMouseUp={handleSquareRightClick}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
{customSquareStyle && <div style={customSquareStyle} />}
|
{highlightSquareStyle && <div style={highlightSquareStyle} />}
|
||||||
|
{playableSquareStyle && <div style={playableSquareStyle} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ export const playerColorAtom = atom<Color>(Color.White);
|
|||||||
export const engineSkillLevelAtom = atom<number>(1);
|
export const engineSkillLevelAtom = atom<number>(1);
|
||||||
|
|
||||||
export const clickedSquaresAtom = atom<string[]>([]);
|
export const clickedSquaresAtom = atom<string[]>([]);
|
||||||
|
export const playableSquaresAtom = atom<string[]>([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user