"use client"; import { useRef, useEffect } from "react"; import { Piece, Position } from "../types/chess"; import ChessPiece from "./ChessPiece"; interface ChessBoardProps { pieces: Piece[]; selectedPieceId?: string; validMoves: Position[]; lastMove?: { from: Position; to: Position }; onPieceClick: (piece: Piece) => void; onEmptyClick: (pos: Position) => void; } // 棋盘配置 const GRID_SIZE = 55; // 格子大小 const PADDING = 40; // 边距 const BOARD_WIDTH = GRID_SIZE * 8 + PADDING * 2; const BOARD_HEIGHT = GRID_SIZE * 9 + PADDING * 2; export default function ChessBoard({ pieces, selectedPieceId, validMoves, lastMove, onPieceClick, onEmptyClick, }: ChessBoardProps) { const canvasRef = useRef(null); // 绘制棋盘网格 useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制背景 ctx.fillStyle = "#fff9e6"; ctx.fillRect(0, 0, canvas.width, canvas.height); // 设置线条样式 ctx.strokeStyle = "#666666"; ctx.lineWidth = 1.2; // 绘制横线 for (let r = 0; r < 10; r++) { ctx.beginPath(); ctx.moveTo(PADDING, PADDING + r * GRID_SIZE); ctx.lineTo(PADDING + 8 * GRID_SIZE, PADDING + r * GRID_SIZE); ctx.stroke(); } // 绘制竖线(上半部分) for (let c = 0; c < 9; c++) { ctx.beginPath(); ctx.moveTo(PADDING + c * GRID_SIZE, PADDING); ctx.lineTo(PADDING + c * GRID_SIZE, PADDING + 4 * GRID_SIZE); ctx.stroke(); } // 绘制竖线(下半部分) for (let c = 0; c < 9; c++) { ctx.beginPath(); ctx.moveTo(PADDING + c * GRID_SIZE, PADDING + 5 * GRID_SIZE); ctx.lineTo(PADDING + c * GRID_SIZE, PADDING + 9 * GRID_SIZE); ctx.stroke(); } // 绘制九宫格斜线(黑方) ctx.beginPath(); ctx.moveTo(PADDING + 3 * GRID_SIZE, PADDING); ctx.lineTo(PADDING + 5 * GRID_SIZE, PADDING + 2 * GRID_SIZE); ctx.stroke(); ctx.beginPath(); ctx.moveTo(PADDING + 5 * GRID_SIZE, PADDING); ctx.lineTo(PADDING + 3 * GRID_SIZE, PADDING + 2 * GRID_SIZE); ctx.stroke(); // 绘制九宫格斜线(红方) ctx.beginPath(); ctx.moveTo(PADDING + 3 * GRID_SIZE, PADDING + 7 * GRID_SIZE); ctx.lineTo(PADDING + 5 * GRID_SIZE, PADDING + 9 * GRID_SIZE); ctx.stroke(); ctx.beginPath(); ctx.moveTo(PADDING + 5 * GRID_SIZE, PADDING + 7 * GRID_SIZE); ctx.lineTo(PADDING + 3 * GRID_SIZE, PADDING + 9 * GRID_SIZE); ctx.stroke(); // 绘制炮位和兵位标记 drawPositionMarkers(ctx); // 绘制楚河汉界 ctx.font = "bold 28px serif"; ctx.fillStyle = "#8b7355"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("楚 河", PADDING + 2 * GRID_SIZE, PADDING + 4.5 * GRID_SIZE); ctx.fillText("汉 界", PADDING + 6 * GRID_SIZE, PADDING + 4.5 * GRID_SIZE); // 绘制上一步移动标记 if (lastMove) { drawMoveMarker(ctx, lastMove.from, "#4a90d9"); drawMoveMarker(ctx, lastMove.to, "#4a90d9"); } // 绘制可移动位置标记 for (const move of validMoves) { drawValidMoveMarker(ctx, move); } }, [validMoves, lastMove]); // 绘制位置标记(炮位和兵位) const drawPositionMarkers = (ctx: CanvasRenderingContext2D) => { ctx.strokeStyle = "#666666"; ctx.lineWidth = 1; const markerSize = 6; const positions = [ // 黑方炮位 { row: 2, col: 1, left: true, right: true, top: false, bottom: true }, { row: 2, col: 7, left: true, right: true, top: false, bottom: true }, // 黑方兵位 { row: 3, col: 0, left: false, right: true, top: true, bottom: true }, { row: 3, col: 2, left: true, right: true, top: true, bottom: true }, { row: 3, col: 4, left: true, right: true, top: true, bottom: true }, { row: 3, col: 6, left: true, right: true, top: true, bottom: true }, { row: 3, col: 8, left: true, right: false, top: true, bottom: true }, // 红方兵位 { row: 6, col: 0, left: false, right: true, top: true, bottom: true }, { row: 6, col: 2, left: true, right: true, top: true, bottom: true }, { row: 6, col: 4, left: true, right: true, top: true, bottom: true }, { row: 6, col: 6, left: true, right: true, top: true, bottom: true }, { row: 6, col: 8, left: true, right: false, top: true, bottom: true }, // 红方炮位 { row: 7, col: 1, left: true, right: true, top: true, bottom: false }, { row: 7, col: 7, left: true, right: true, top: true, bottom: false }, ]; for (const pos of positions) { const x = PADDING + pos.col * GRID_SIZE; const y = PADDING + pos.row * GRID_SIZE; ctx.beginPath(); if (pos.left) { ctx.moveTo(x - markerSize, y - markerSize / 2); ctx.lineTo(x - markerSize / 2, y - markerSize / 2); ctx.lineTo(x - markerSize / 2, y - markerSize); ctx.moveTo(x - markerSize, y + markerSize / 2); ctx.lineTo(x - markerSize / 2, y + markerSize / 2); ctx.lineTo(x - markerSize / 2, y + markerSize); } if (pos.right) { ctx.moveTo(x + markerSize, y - markerSize / 2); ctx.lineTo(x + markerSize / 2, y - markerSize / 2); ctx.lineTo(x + markerSize / 2, y - markerSize); ctx.moveTo(x + markerSize, y + markerSize / 2); ctx.lineTo(x + markerSize / 2, y + markerSize / 2); ctx.lineTo(x + markerSize / 2, y + markerSize); } ctx.stroke(); } }; // 绘制移动标记 const drawMoveMarker = ( ctx: CanvasRenderingContext2D, pos: Position, color: string ) => { const x = PADDING + pos.col * GRID_SIZE; const y = PADDING + pos.row * GRID_SIZE; const size = 8; ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.stroke(); }; // 绘制可移动位置标记 const drawValidMoveMarker = (ctx: CanvasRenderingContext2D, pos: Position) => { const x = PADDING + pos.col * GRID_SIZE; const y = PADDING + pos.row * GRID_SIZE; ctx.fillStyle = "rgba(0, 150, 0, 0.4)"; ctx.beginPath(); ctx.arc(x, y, 6, 0, Math.PI * 2); ctx.fill(); }; // 处理点击事件 const handleClick = (e: React.MouseEvent) => { const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const col = Math.round((x - PADDING) / GRID_SIZE); const row = Math.round((y - PADDING) / GRID_SIZE); if (col < 0 || col > 8 || row < 0 || row > 9) return; const clickedPiece = pieces.find( (p) => p.alive && p.position.row === row && p.position.col === col ); if (clickedPiece) { onPieceClick(clickedPiece); } else { onEmptyClick({ row, col }); } }; return (
{/* 棋盘画布 */} {/* 棋子层 */}
{pieces .filter((p) => p.alive) .map((piece) => (
onPieceClick(piece)} size={44} />
))}
); } export { GRID_SIZE, PADDING, BOARD_WIDTH, BOARD_HEIGHT };