Files
nanocode/happy-games/games/chinese-chess/app/hooks/useChessGame.ts
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

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,
};
}