import wp from "../assets/pieces/wp.png"; import wn from "../assets/pieces/wn.png"; import wb from "../assets/pieces/wb.png"; import wr from "../assets/pieces/wr.png"; import wq from "../assets/pieces/wq.png"; import wk from "../assets/pieces/wk.png"; import bp from "../assets/pieces/bp.png"; import bn from "../assets/pieces/bn.png"; import bb from "../assets/pieces/bb.png"; import br from "../assets/pieces/br.png"; import bq from "../assets/pieces/bq.png"; import bk from "../assets/pieces/bk.png"; import Chess, { squareColor, Piece, PieceType, Color, algebraic, file, rank, FILE, RANK, squareIndex, Move, MOVE_FLAGS, PIECE_PROMOTION, PiecePromotionType, } from "../../../server/src/chess/engine"; import { MouseEventHandler, useState } from "react"; import Show from "../utils/Show"; import useWindowSize from "../utils/useWindowSize"; const PIECES: Record> = { w: { p: wp, n: wn, b: wb, r: wr, q: wq, k: wk }, b: { p: bp, n: bn, b: bb, r: br, q: bq, k: bk }, }; const ChessBoard: React.FC<{ chess: Chess; sendMove: (move: Move) => void; blackPerspective?: boolean; }> = ({ chess, sendMove, blackPerspective }) => { const { width, height } = useWindowSize(); const [activeTile, setActiveTile] = useState(-1); const [promotionMove, setPromotionMove] = useState< (Pick & { color: Color }) | null >(null); const gridSize = Math.min(width, height, 800); const tileProps = new Array(64).fill(null).map((_, i) => ({ tileNumber: i, piece: chess.getPiece(i), isAttacked: false, isPromotion: false, isActive: false, })); if (activeTile != -1) { tileProps[activeTile].isActive = true; chess.getMovesForSquare(algebraic(activeTile)).forEach(({ to, flags }) => { const square = squareIndex(to); tileProps[square].isAttacked = true; tileProps[square].isPromotion = flags & MOVE_FLAGS.PROMOTION ? true : false; }); } const tiles = tileProps.map((props, i) => ( )); const handleClick: MouseEventHandler = (e) => { if (promotionMove != null) return; // TODO: fix types // @ts-ignore const tile = parseInt(e.target.dataset.tile); if (isNaN(tile)) return; if (activeTile != -1 && tileProps[tile].isAttacked) { const moveObj = { from: algebraic(activeTile), to: algebraic(tile), color: (chess.getPiece(activeTile) as Piece).color, }; if (tileProps[tile].isPromotion) return setPromotionMove(moveObj); sendMove(moveObj); setActiveTile(-1); } if (tileProps[tile].piece == null) return setActiveTile(-1); if (tile == activeTile) setActiveTile(-1); else setActiveTile(tile); }; const sendPromotionMove = (promotion: PiecePromotionType) => { if (promotionMove == null) return; const moveObj: Move = { from: promotionMove.from, to: promotionMove.to, promotion, }; sendMove(moveObj); setPromotionMove(null); setActiveTile(-1); }; return (
{blackPerspective == true ? tiles.reverse() : tiles}
{promotionMove == null ? ( <> ) : (
{PIECE_PROMOTION.map((p) => ( sendPromotionMove(p)} /> ))}
)}
); }; const TILE_COLORS = Object.freeze({ w: { bg: "bg-white-tile", text: "text-black-tile", active: "bg-sky-400", }, b: { bg: "bg-black-tile", text: "text-white-tile", active: "bg-sky-700", }, } as const); const Tile: React.FC<{ tileNumber: number; piece: Piece | null; isActive: boolean; isAttacked: boolean; blackPerspective?: boolean; }> = ({ tileNumber, piece, isActive, isAttacked, blackPerspective }) => { const color = squareColor(tileNumber); const { bg: bgColor, text: textColor } = TILE_COLORS[color]; const activeColor = isActive ? TILE_COLORS[color].active : ""; const tileFile = file(tileNumber); const tileRank = rank(tileNumber); const square = algebraic(tileNumber); const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A); const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST); return (
*]:pointer-events-none ${bgColor} ${activeColor}`} data-tile={tileNumber} > {piece == null ? <> : }
} >
{square[1]}
{square[0]}
); }; export default ChessBoard;