339 lines
9.9 KiB
TypeScript
339 lines
9.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
import {
|
|
Piece,
|
|
Position,
|
|
Color,
|
|
GameMode,
|
|
Difficulty,
|
|
GameStatus,
|
|
Move,
|
|
INITIAL_PIECES,
|
|
} from "../types/chess";
|
|
import {
|
|
getValidMoves,
|
|
isValidMove,
|
|
makeMove,
|
|
isInCheck,
|
|
isCheckmate,
|
|
isStalemate,
|
|
getPieceAt,
|
|
} from "../lib/chessRules";
|
|
import { findBestMove } from "../lib/astarAI";
|
|
import { getDeepseekMove } from "../lib/deepseekAPI";
|
|
|
|
// 生成唯一ID
|
|
let pieceIdCounter = 0;
|
|
const generatePieceId = () => `piece-${++pieceIdCounter}`;
|
|
|
|
// 初始化棋子
|
|
const initializePieces = (): Piece[] => {
|
|
return INITIAL_PIECES.map((p) => ({
|
|
...p,
|
|
id: generatePieceId(),
|
|
}));
|
|
};
|
|
|
|
export function useChessGame() {
|
|
// 游戏状态
|
|
const [pieces, setPieces] = useState<Piece[]>(initializePieces());
|
|
const [turn, setTurn] = useState<Color>("red");
|
|
const [status, setStatus] = useState<GameStatus>("playing");
|
|
const [gameStarted, setGameStarted] = useState<boolean>(false);
|
|
const [winner, setWinner] = useState<Color | undefined>(undefined);
|
|
const [selectedPieceId, setSelectedPieceId] = useState<string | undefined>(
|
|
undefined
|
|
);
|
|
const [validMoves, setValidMoves] = useState<Position[]>([]);
|
|
const [moveHistory, setMoveHistory] = useState<Move[]>([]);
|
|
const [gameMode, setGameMode] = useState<GameMode>("人vsA*");
|
|
const [difficulty, setDifficulty] = useState<Difficulty>("一般");
|
|
const [lastMove, setLastMove] = useState<{ from: Position; to: Position } | undefined>(undefined);
|
|
const [isAIThinking, setIsAIThinking] = useState(false);
|
|
|
|
// 用于保存历史状态以支持悔棋
|
|
const historyRef = useRef<
|
|
{ pieces: Piece[]; turn: Color; status: GameStatus; moveHistory: Move[] }[]
|
|
>([]);
|
|
|
|
// 获取选中的棋子
|
|
const selectedPiece = selectedPieceId
|
|
? pieces.find((p) => p.id === selectedPieceId)
|
|
: undefined;
|
|
|
|
// 更新游戏状态(将军/将死/困毙检测)
|
|
const updateGameStatus = useCallback(
|
|
(currentPieces: Piece[], currentTurn: Color) => {
|
|
const nextTurn = currentTurn === "red" ? "black" : "red";
|
|
|
|
if (isCheckmate(currentPieces, nextTurn)) {
|
|
setStatus("checkmate");
|
|
setWinner(currentTurn);
|
|
return "checkmate";
|
|
} else if (isStalemate(currentPieces, nextTurn)) {
|
|
setStatus("stalemate");
|
|
return "stalemate";
|
|
} else if (isInCheck(currentPieces, nextTurn)) {
|
|
setStatus("check");
|
|
return "check";
|
|
} else {
|
|
setStatus("playing");
|
|
return "playing";
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
// 保存当前状态到历史
|
|
const saveToHistory = useCallback(() => {
|
|
historyRef.current.push({
|
|
pieces: JSON.parse(JSON.stringify(pieces)),
|
|
turn,
|
|
status,
|
|
moveHistory: [...moveHistory],
|
|
});
|
|
// 只保留最近20步历史
|
|
if (historyRef.current.length > 20) {
|
|
historyRef.current.shift();
|
|
}
|
|
}, [pieces, turn, status, moveHistory]);
|
|
|
|
// 处理棋子点击
|
|
const handlePieceClick = useCallback(
|
|
(piece: Piece) => {
|
|
if (!gameStarted) return;
|
|
if (status !== "playing" && status !== "check") return;
|
|
if (isAIThinking) return;
|
|
|
|
// 如果点击的是己方棋子,选中它
|
|
if (piece.color === turn) {
|
|
setSelectedPieceId(piece.id);
|
|
setValidMoves(getValidMoves(pieces, piece));
|
|
return;
|
|
}
|
|
|
|
// 如果点击的是对方棋子且已选中己方棋子,尝试吃子
|
|
if (selectedPiece && selectedPiece.color === turn) {
|
|
const validation = isValidMove(
|
|
pieces,
|
|
selectedPiece.position,
|
|
piece.position,
|
|
turn
|
|
);
|
|
if (validation.valid) {
|
|
saveToHistory();
|
|
const { pieces: newPieces, move } = makeMove(
|
|
pieces,
|
|
selectedPiece.position,
|
|
piece.position
|
|
);
|
|
setPieces(newPieces);
|
|
setMoveHistory((prev) => [...prev, move]);
|
|
setLastMove({ from: selectedPiece.position, to: piece.position });
|
|
setSelectedPieceId(undefined);
|
|
setValidMoves([]);
|
|
|
|
const newStatus = updateGameStatus(newPieces, turn);
|
|
if (newStatus === "playing" || newStatus === "check") {
|
|
setTurn(turn === "red" ? "black" : "red");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
[pieces, turn, selectedPiece, status, isAIThinking, saveToHistory, updateGameStatus]
|
|
);
|
|
|
|
// 处理空白位置点击
|
|
const handleEmptyClick = useCallback(
|
|
(pos: Position) => {
|
|
if (!gameStarted) return;
|
|
if (status !== "playing" && status !== "check") return;
|
|
if (isAIThinking) return;
|
|
if (!selectedPiece) return;
|
|
|
|
const validation = isValidMove(
|
|
pieces,
|
|
selectedPiece.position,
|
|
pos,
|
|
turn
|
|
);
|
|
if (validation.valid) {
|
|
saveToHistory();
|
|
const { pieces: newPieces, move } = makeMove(
|
|
pieces,
|
|
selectedPiece.position,
|
|
pos
|
|
);
|
|
setPieces(newPieces);
|
|
setMoveHistory((prev) => [...prev, move]);
|
|
setLastMove({ from: selectedPiece.position, to: pos });
|
|
setSelectedPieceId(undefined);
|
|
setValidMoves([]);
|
|
|
|
const newStatus = updateGameStatus(newPieces, turn);
|
|
if (newStatus === "playing" || newStatus === "check") {
|
|
setTurn(turn === "red" ? "black" : "red");
|
|
}
|
|
}
|
|
},
|
|
[pieces, turn, selectedPiece, status, isAIThinking, saveToHistory, updateGameStatus]
|
|
);
|
|
|
|
// AI走棋
|
|
const makeAIMove = useCallback(async () => {
|
|
if (status !== "playing" && status !== "check") return;
|
|
setIsAIThinking(true);
|
|
|
|
try {
|
|
let aiMove = null;
|
|
|
|
// 根据游戏模式决定使用哪个AI
|
|
const isAStarTurn =
|
|
(gameMode === "人vsA*" && turn === "black") ||
|
|
(gameMode === "A*vsDeepseek" && turn === "red");
|
|
|
|
const isDeepseekTurn =
|
|
(gameMode === "人vsDeepseek" && turn === "black") ||
|
|
(gameMode === "A*vsDeepseek" && turn === "black");
|
|
|
|
if (isAStarTurn) {
|
|
// 使用A* AI
|
|
console.log(`A* AI思考中... 当前回合: ${turn}, 难度: ${difficulty}`);
|
|
aiMove = findBestMove(pieces, turn, difficulty);
|
|
console.log(`A* AI走法:`, aiMove);
|
|
} else if (isDeepseekTurn) {
|
|
// 使用Deepseek API
|
|
const moveHistoryStr = moveHistory.map((m) => m.notation);
|
|
aiMove = await getDeepseekMove(pieces, turn, difficulty, moveHistoryStr);
|
|
}
|
|
|
|
if (aiMove) {
|
|
saveToHistory();
|
|
const { pieces: newPieces, move } = makeMove(
|
|
pieces,
|
|
aiMove.from,
|
|
aiMove.to
|
|
);
|
|
setPieces(newPieces);
|
|
setMoveHistory((prev) => [...prev, move]);
|
|
setLastMove({ from: aiMove.from, to: aiMove.to });
|
|
|
|
const newStatus = updateGameStatus(newPieces, turn);
|
|
if (newStatus === "playing" || newStatus === "check") {
|
|
setTurn(turn === "red" ? "black" : "red");
|
|
}
|
|
}
|
|
} finally {
|
|
setIsAIThinking(false);
|
|
}
|
|
}, [pieces, turn, status, gameMode, difficulty, moveHistory, saveToHistory, updateGameStatus]);
|
|
|
|
// AI自动走棋效果
|
|
useEffect(() => {
|
|
// 游戏未开始时,AI不走棋
|
|
if (!gameStarted) return;
|
|
|
|
const isAITurn =
|
|
(gameMode === "人vsA*" && turn === "black") ||
|
|
(gameMode === "人vsDeepseek" && turn === "black") ||
|
|
gameMode === "A*vsDeepseek";
|
|
|
|
if (isAITurn && (status === "playing" || status === "check")) {
|
|
const timer = setTimeout(() => {
|
|
makeAIMove();
|
|
}, 500);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [turn, status, gameMode, gameStarted, makeAIMove]);
|
|
|
|
// 开始新游戏
|
|
const newGame = useCallback(() => {
|
|
setPieces(initializePieces());
|
|
setTurn("red");
|
|
setStatus("playing");
|
|
setWinner(undefined);
|
|
setSelectedPieceId(undefined);
|
|
setValidMoves([]);
|
|
setMoveHistory([]);
|
|
setLastMove(undefined);
|
|
setGameStarted(true);
|
|
historyRef.current = [];
|
|
pieceIdCounter = 0;
|
|
}, []);
|
|
|
|
// 开始棋局(从准备状态进入游戏)
|
|
const startGame = useCallback(() => {
|
|
console.log("[Chess] Starting game...");
|
|
setGameStarted(true);
|
|
}, []);
|
|
|
|
// 悔棋
|
|
const undo = useCallback(() => {
|
|
if (historyRef.current.length === 0) return;
|
|
|
|
const lastState = historyRef.current.pop();
|
|
if (lastState) {
|
|
setPieces(lastState.pieces);
|
|
setTurn(lastState.turn);
|
|
setStatus(lastState.status);
|
|
setMoveHistory(lastState.moveHistory);
|
|
setSelectedPieceId(undefined);
|
|
setValidMoves([]);
|
|
}
|
|
}, []);
|
|
|
|
// 认输
|
|
const surrender = useCallback(() => {
|
|
setStatus("checkmate");
|
|
setWinner(turn === "red" ? "black" : "red");
|
|
}, [turn]);
|
|
|
|
// 关闭弹窗
|
|
const closeModal = useCallback(() => {
|
|
// 弹窗关闭不做特殊处理,用户可以点击新局开始新游戏
|
|
}, []);
|
|
|
|
// 获取当前玩家类型
|
|
const getCurrentPlayerType = useCallback(() => {
|
|
if (gameMode === "A*vsDeepseek") {
|
|
return turn === "red" ? "A* AI" : "Deepseek AI";
|
|
}
|
|
if (turn === "red") return "玩家";
|
|
return gameMode === "人vsA*" ? "A* AI" : "Deepseek AI";
|
|
}, [turn, gameMode]);
|
|
|
|
return {
|
|
// 状态
|
|
pieces,
|
|
turn,
|
|
status,
|
|
winner,
|
|
selectedPieceId,
|
|
validMoves,
|
|
moveHistory,
|
|
gameMode,
|
|
difficulty,
|
|
lastMove,
|
|
isAIThinking,
|
|
gameStarted,
|
|
|
|
// 派生状态
|
|
selectedPiece,
|
|
canUndo: historyRef.current.length > 0,
|
|
isPlaying: status === "playing" || status === "check",
|
|
currentPlayerType: getCurrentPlayerType(),
|
|
|
|
// 操作
|
|
handlePieceClick,
|
|
handleEmptyClick,
|
|
newGame,
|
|
undo,
|
|
surrender,
|
|
closeModal,
|
|
setGameMode,
|
|
setDifficulty,
|
|
startGame,
|
|
};
|
|
}
|