From 4864bf61f52a5fc565f362fdcf21dd3528b53b47 Mon Sep 17 00:00:00 2001 From: GuillaumeSD Date: Tue, 19 Mar 2024 01:56:35 +0100 Subject: [PATCH] feat : add piece drag hint --- src/lib/engine/uciEngine.ts | 6 +- src/sections/analysis/board/index.tsx | 27 +++++++++ .../analysis/board/squareRenderer.tsx | 59 +++++++----------- src/sections/analysis/states.ts | 1 + src/sections/play/board/index.tsx | 41 +++++++++++-- src/sections/play/board/squareRenderer.tsx | 60 +++++++------------ src/sections/play/states.ts | 1 + 7 files changed, 113 insertions(+), 82 deletions(-) diff --git a/src/lib/engine/uciEngine.ts b/src/lib/engine/uciEngine.ts index 0da3c3b..48d2e15 100644 --- a/src/lib/engine/uciEngine.ts +++ b/src/lib/engine/uciEngine.ts @@ -90,7 +90,7 @@ export abstract class UciEngine { return this.ready; } - private async stopSearch(): Promise { + public async stopSearch(): Promise { await this.sendCommands(["stop", "isready"], "readyok"); } @@ -243,7 +243,7 @@ export abstract class UciEngine { fen: string, skillLevel: number, depth = 16 - ): Promise { + ): Promise { this.throwErrorIfNotReady(); await this.setSkillLevel(skillLevel); @@ -260,6 +260,6 @@ export abstract class UciEngine { throw new Error("No move found"); } - return move; + return move === "(none)" ? undefined : move; } } diff --git a/src/sections/analysis/board/index.tsx b/src/sections/analysis/board/index.tsx index e93201f..c65546d 100644 --- a/src/sections/analysis/board/index.tsx +++ b/src/sections/analysis/board/index.tsx @@ -6,6 +6,7 @@ import { boardOrientationAtom, clickedSquaresAtom, currentPositionAtom, + playableSquaresAtom, showBestMoveArrowAtom, } from "../states"; import { Arrow, Square } from "react-chessboard/dist/chessboard/types"; @@ -26,6 +27,7 @@ export default function Board() { const { makeMove: makeBoardMove } = useChessActions(boardAtom); const position = useAtomValue(currentPositionAtom); const setClickedSquares = useSetAtom(clickedSquaresAtom); + const setPlayableSquares = useSetAtom(playableSquaresAtom); 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 bestMove = position?.lastEval?.bestMove; const moveClassification = position?.eval?.moveClassification; @@ -113,6 +136,10 @@ export default function Board() { boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)", }} customSquare={SquareRenderer} + onSquareClick={handleSquareLeftClick} + onSquareRightClick={handleSquareRightClick} + onPieceDragBegin={handlePieceDragBegin} + onPieceDragEnd={handlePieceDragEnd} /> diff --git a/src/sections/analysis/board/squareRenderer.tsx b/src/sections/analysis/board/squareRenderer.tsx index 4bab9f9..22eec3f 100644 --- a/src/sections/analysis/board/squareRenderer.tsx +++ b/src/sections/analysis/board/squareRenderer.tsx @@ -1,31 +1,28 @@ import { clickedSquaresAtom, currentPositionAtom, + playableSquaresAtom, showPlayerMoveIconAtom, } from "../states"; import { MoveClassification } from "@/types/enums"; -import { atom, useAtom, useAtomValue } from "jotai"; +import { useAtomValue } from "jotai"; import Image from "next/image"; -import { CSSProperties, MouseEventHandler, forwardRef } from "react"; +import { CSSProperties, forwardRef } from "react"; import { CustomSquareProps } from "react-chessboard/dist/chessboard/types"; -const rightClickEventSquareAtom = atom(null); - const SquareRenderer = forwardRef( (props, ref) => { const { children, square, style } = props; const showPlayerMoveIcon = useAtomValue(showPlayerMoveIconAtom); const position = useAtomValue(currentPositionAtom); - const [clickedSquares, setClickedSquares] = useAtom(clickedSquaresAtom); - const [rightClickEventSquare, setRightClickEventSquare] = useAtom( - rightClickEventSquareAtom - ); + const clickedSquares = useAtomValue(clickedSquaresAtom); + const playableSquares = useAtomValue(playableSquaresAtom); const fromSquare = position.lastMove?.from; const toSquare = position.lastMove?.to; const moveClassification = position?.eval?.moveClassification; - const customSquareStyle: CSSProperties | undefined = + const highlightSquareStyle: CSSProperties | undefined = clickedSquares.includes(square) ? { position: "absolute", @@ -46,37 +43,25 @@ const SquareRenderer = forwardRef( } : undefined; - const handleSquareLeftClick: MouseEventHandler = () => { - setClickedSquares([]); - }; - - const handleSquareRightClick: MouseEventHandler = ( - event - ) => { - if (event.button !== 2) return; - - if (rightClickEventSquare !== square) { - setRightClickEventSquare(null); - return; - } - - setClickedSquares((prev) => - prev.includes(square) - ? prev.filter((s) => s !== square) - : [...prev, square] - ); - }; + const playableSquareStyle: CSSProperties | undefined = + playableSquares.includes(square) + ? { + position: "absolute", + width: "100%", + height: "100%", + backgroundColor: "rgba(0,0,0,.14)", + padding: "35%", + backgroundClip: "content-box", + borderRadius: "50%", + boxSizing: "border-box", + } + : undefined; return ( -
e.button === 2 && setRightClickEventSquare(square)} - onMouseUp={handleSquareRightClick} - > +
{children} - {customSquareStyle &&
} + {highlightSquareStyle &&
} + {playableSquareStyle &&
} {moveClassification && showPlayerMoveIcon && square === toSquare && ( ([]); +export const playableSquaresAtom = atom([]); export const engineDepthAtom = atom(16); export const engineMultiPvAtom = atom(3); diff --git a/src/sections/play/board/index.tsx b/src/sections/play/board/index.tsx index 15a6bfc..204767d 100644 --- a/src/sections/play/board/index.tsx +++ b/src/sections/play/board/index.tsx @@ -5,6 +5,7 @@ import { clickedSquaresAtom, engineSkillLevelAtom, gameAtom, + playableSquaresAtom, playerColorAtom, } from "../states"; import { Square } from "react-chessboard/dist/chessboard/types"; @@ -24,23 +25,30 @@ export default function Board() { const playerColor = useAtomValue(playerColorAtom); const { makeMove: makeBoardMove } = useChessActions(gameAtom); const setClickedSquares = useSetAtom(clickedSquaresAtom); + const setPlayableSquares = useSetAtom(playableSquaresAtom); const engineSkillLevel = useAtomValue(engineSkillLevelAtom); const engine = useEngine(EngineName.Stockfish16); const gameFen = game.fen(); - const turn = game.turn(); + const isGameFinished = game.isGameOver(); useEffect(() => { const playEngineMove = async () => { - if (!engine?.isReady() || turn === playerColor) return; + if (!engine?.isReady() || game.turn() === playerColor || isGameFinished) { + return; + } const move = await engine.getEngineNextMove( gameFen, engineSkillLevel - 1 ); - makeBoardMove(uciMoveParams(move)); + if (move) makeBoardMove(uciMoveParams(move)); }; playEngineMove(); - }, [engine, turn, engine, playerColor, engineSkillLevel]); + + return () => { + engine?.stopSearch(); + }; + }, [gameFen, engine]); useEffect(() => { setClickedSquares([]); @@ -70,6 +78,27 @@ export default function Board() { 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 ( diff --git a/src/sections/play/board/squareRenderer.tsx b/src/sections/play/board/squareRenderer.tsx index e83e458..446a2ad 100644 --- a/src/sections/play/board/squareRenderer.tsx +++ b/src/sections/play/board/squareRenderer.tsx @@ -1,24 +1,20 @@ -import { clickedSquaresAtom, gameAtom } from "../states"; -import { atom, useAtom, useAtomValue } from "jotai"; -import { CSSProperties, MouseEventHandler, forwardRef } from "react"; +import { clickedSquaresAtom, gameAtom, playableSquaresAtom } from "../states"; +import { useAtomValue } from "jotai"; +import { CSSProperties, forwardRef } from "react"; import { CustomSquareProps } from "react-chessboard/dist/chessboard/types"; -const rightClickEventSquareAtom = atom(null); - const SquareRenderer = forwardRef( (props, ref) => { const { children, square, style } = props; const game = useAtomValue(gameAtom); - const [clickedSquares, setClickedSquares] = useAtom(clickedSquaresAtom); - const [rightClickEventSquare, setRightClickEventSquare] = useAtom( - rightClickEventSquareAtom - ); + const clickedSquares = useAtomValue(clickedSquaresAtom); + const playableSquares = useAtomValue(playableSquaresAtom); const lastMove = game.history({ verbose: true }).at(-1); const fromSquare = lastMove?.from; const toSquare = lastMove?.to; - const customSquareStyle: CSSProperties | undefined = + const highlightSquareStyle: CSSProperties | undefined = clickedSquares.includes(square) ? { position: "absolute", @@ -37,37 +33,25 @@ const SquareRenderer = forwardRef( } : undefined; - const handleSquareLeftClick: MouseEventHandler = () => { - setClickedSquares([]); - }; - - const handleSquareRightClick: MouseEventHandler = ( - event - ) => { - if (event.button !== 2) return; - - if (rightClickEventSquare !== square) { - setRightClickEventSquare(null); - return; - } - - setClickedSquares((prev) => - prev.includes(square) - ? prev.filter((s) => s !== square) - : [...prev, square] - ); - }; + const playableSquareStyle: CSSProperties | undefined = + playableSquares.includes(square) + ? { + position: "absolute", + width: "100%", + height: "100%", + backgroundColor: "rgba(0,0,0,.14)", + padding: "35%", + backgroundClip: "content-box", + borderRadius: "50%", + boxSizing: "border-box", + } + : undefined; return ( -
e.button === 2 && setRightClickEventSquare(square)} - onMouseUp={handleSquareRightClick} - > +
{children} - {customSquareStyle &&
} + {highlightSquareStyle &&
} + {playableSquareStyle &&
}
); } diff --git a/src/sections/play/states.ts b/src/sections/play/states.ts index 6c212e9..ee1694d 100644 --- a/src/sections/play/states.ts +++ b/src/sections/play/states.ts @@ -7,3 +7,4 @@ export const playerColorAtom = atom(Color.White); export const engineSkillLevelAtom = atom(1); export const clickedSquaresAtom = atom([]); +export const playableSquaresAtom = atom([]);