Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

361 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Piece, Position, Color, PieceType, Move, PALACE, RIVER_ROWS } from "../types/chess";
// 获取棋盘上的棋子
export function getPieceAt(pieces: Piece[], pos: Position): Piece | undefined {
return pieces.find((p) => p.alive && p.position.row === pos.row && p.position.col === pos.col);
}
// 检查位置是否在棋盘内
export function isInBoard(pos: Position): boolean {
return pos.row >= 0 && pos.row <= 9 && pos.col >= 0 && pos.col <= 8;
}
// 检查位置是否在九宫格内
export function isInPalace(pos: Position, color: Color): boolean {
const palace = PALACE[color];
return (
pos.row >= palace.minRow &&
pos.row <= palace.maxRow &&
pos.col >= palace.minCol &&
pos.col <= palace.maxCol
);
}
// 检查是否过河
export function hasCrossedRiver(pos: Position, color: Color): boolean {
if (color === "red") {
return pos.row <= 4; // 红方向上过河
} else {
return pos.row >= 5; // 黑方向下过河
}
}
// 计算两个位置之间的棋子数(用于车和炮)
export function countPiecesBetween(pieces: Piece[], from: Position, to: Position): number {
if (from.row !== to.row && from.col !== to.col) return -1; // 不在同一直线
let count = 0;
if (from.row === to.row) {
// 横向
const minCol = Math.min(from.col, to.col);
const maxCol = Math.max(from.col, to.col);
for (let col = minCol + 1; col < maxCol; col++) {
if (getPieceAt(pieces, { row: from.row, col })) count++;
}
} else {
// 纵向
const minRow = Math.min(from.row, to.row);
const maxRow = Math.max(from.row, to.row);
for (let row = minRow + 1; row < maxRow; row++) {
if (getPieceAt(pieces, { row, col: from.col })) count++;
}
}
return count;
}
// 检查马腿是否被挡住
export function isHorseBlocked(pieces: Piece[], from: Position, to: Position): boolean {
const rowDiff = to.row - from.row;
const colDiff = to.col - from.col;
// 马走日:必须是一个方向2格,另一个方向1格
if (Math.abs(rowDiff) === 2 && Math.abs(colDiff) === 1) {
// 纵向移动2格,检查中间点
const blockPos: Position = { row: from.row + rowDiff / 2, col: from.col };
return !!getPieceAt(pieces, blockPos);
} else if (Math.abs(rowDiff) === 1 && Math.abs(colDiff) === 2) {
// 横向移动2格,检查中间点
const blockPos: Position = { row: from.row, col: from.col + colDiff / 2 };
return !!getPieceAt(pieces, blockPos);
}
return true; // 不是日字走法,视为被挡住
}
// 检查象眼是否被挡住
export function isElephantBlocked(pieces: Piece[], from: Position, to: Position): boolean {
const blockPos: Position = {
row: (from.row + to.row) / 2,
col: (from.col + to.col) / 2,
};
return !!getPieceAt(pieces, blockPos);
}
// 验证特定棋子的走法
export function isValidPieceMove(
pieces: Piece[],
piece: Piece,
to: Position
): boolean {
const from = piece.position;
const targetPiece = getPieceAt(pieces, to);
// 不能吃自己的棋子
if (targetPiece && targetPiece.color === piece.color) return false;
const rowDiff = to.row - from.row;
const colDiff = to.col - from.col;
const absRowDiff = Math.abs(rowDiff);
const absColDiff = Math.abs(colDiff);
switch (piece.type) {
case "帅":
case "将":
// 只能在九宫格内移动
if (!isInPalace(to, piece.color)) {
// 特殊情况:将帅对面(飞将)
if (colDiff === 0) {
const target = getPieceAt(pieces, to);
if (target && (target.type === "帅" || target.type === "将")) {
return countPiecesBetween(pieces, from, to) === 0;
}
}
return false;
}
// 只能横竖走一格
return (absRowDiff === 1 && absColDiff === 0) || (absRowDiff === 0 && absColDiff === 1);
case "仕":
case "士":
// 只能在九宫格内移动
if (!isInPalace(to, piece.color)) return false;
// 只能斜走一格
return absRowDiff === 1 && absColDiff === 1;
case "相":
case "象":
// 不能过河
if (hasCrossedRiver(to, piece.color)) return false;
// 必须斜走2格
if (absRowDiff !== 2 || absColDiff !== 2) return false;
// 不能被象眼挡住
return !isElephantBlocked(pieces, from, to);
case "马":
// 必须走日字
if (!((absRowDiff === 2 && absColDiff === 1) || (absRowDiff === 1 && absColDiff === 2))) {
return false;
}
// 不能被马腿挡住
return !isHorseBlocked(pieces, from, to);
case "车":
// 必须横竖走
if (from.row !== to.row && from.col !== to.col) return false;
// 路径上不能有其他棋子
return countPiecesBetween(pieces, from, to) === 0;
case "炮":
// 必须横竖走
if (from.row !== to.row && from.col !== to.col) return false;
const piecesBetween = countPiecesBetween(pieces, from, to);
if (targetPiece) {
// 吃子:中间必须有且仅有1个棋子
return piecesBetween === 1;
} else {
// 移动:中间不能有棋子
return piecesBetween === 0;
}
case "兵":
case "卒":
// 只能向前走一格(不能后退)
const forwardDir = piece.color === "red" ? -1 : 1;
if (!hasCrossedRiver(from, piece.color)) {
// 未过河:只能向前走
return rowDiff === forwardDir && colDiff === 0;
} else {
// 已过河:可以向前或向左右
return (
(rowDiff === forwardDir && colDiff === 0) || // 向前
(rowDiff === 0 && absColDiff === 1) // 左右
);
}
default:
return false;
}
}
// 检查移动后是否会被将军
export function isInCheckAfterMove(
pieces: Piece[],
from: Position,
to: Position,
color: Color
): boolean {
// 模拟移动
const newPieces = pieces.map((p) => {
if (p.position.row === from.row && p.position.col === from.col) {
return { ...p, position: to };
}
// 移除被吃的棋子
if (p.position.row === to.row && p.position.col === to.col) {
return { ...p, alive: false };
}
return p;
});
return isInCheck(newPieces, color);
}
// 检查某方是否被将军
export function isInCheck(pieces: Piece[], color: Color): boolean {
// 找到己方的帅/将
const king = pieces.find(
(p) => p.alive && p.color === color && (p.type === "帅" || p.type === "将")
);
if (!king) return false;
// 检查对方任何棋子是否能攻击到帅/将
const enemyColor = color === "red" ? "black" : "red";
const enemyPieces = pieces.filter((p) => p.alive && p.color === enemyColor);
for (const enemy of enemyPieces) {
if (isValidPieceMove(pieces, enemy, king.position)) {
return true;
}
}
return false;
}
// 获取某棋子的所有合法移动
export function getValidMoves(pieces: Piece[], piece: Piece): Position[] {
const validMoves: Position[] = [];
for (let row = 0; row <= 9; row++) {
for (let col = 0; col <= 8; col++) {
const to = { row, col };
if (
isValidPieceMove(pieces, piece, to) &&
!isInCheckAfterMove(pieces, piece.position, to, piece.color)
) {
validMoves.push(to);
}
}
}
return validMoves;
}
// 检查某方是否有任何合法移动
export function hasAnyValidMove(pieces: Piece[], color: Color): boolean {
const colorPieces = pieces.filter((p) => p.alive && p.color === color);
for (const piece of colorPieces) {
if (getValidMoves(pieces, piece).length > 0) {
return true;
}
}
return false;
}
// 检查是否将死
export function isCheckmate(pieces: Piece[], color: Color): boolean {
return isInCheck(pieces, color) && !hasAnyValidMove(pieces, color);
}
// 检查是否困毙(无子可动但不是将军)
export function isStalemate(pieces: Piece[], color: Color): boolean {
return !isInCheck(pieces, color) && !hasAnyValidMove(pieces, color);
}
// 验证移动是否合法
export function isValidMove(
pieces: Piece[],
from: Position,
to: Position,
turn: Color
): { valid: boolean; reason?: string } {
const piece = getPieceAt(pieces, from);
if (!piece) return { valid: false, reason: "没有棋子" };
if (piece.color !== turn) return { valid: false, reason: "不是己方棋子" };
if (!isInBoard(to)) return { valid: false, reason: "超出棋盘" };
const targetPiece = getPieceAt(pieces, to);
if (targetPiece && targetPiece.color === piece.color) {
return { valid: false, reason: "不能吃己方棋子" };
}
if (!isValidPieceMove(pieces, piece, to)) {
return { valid: false, reason: "不符合走法规则" };
}
if (isInCheckAfterMove(pieces, from, to, turn)) {
return { valid: false, reason: "移动后会被将军" };
}
return { valid: true };
}
// 执行移动
export function makeMove(
pieces: Piece[],
from: Position,
to: Position
): { pieces: Piece[]; move: Move; capturedPiece?: Piece } {
const piece = getPieceAt(pieces, from)!;
const targetPiece = getPieceAt(pieces, to);
const newPieces = pieces.map((p) => {
if (p.id === piece.id) {
return { ...p, position: to };
}
if (targetPiece && p.id === targetPiece.id) {
return { ...p, alive: false };
}
return p;
});
// 生成走棋记录
const notation = generateNotation(piece, from, to, targetPiece);
const move: Move = {
from,
to,
piece,
capturedPiece: targetPiece,
notation,
};
return { pieces: newPieces, move, capturedPiece: targetPiece };
}
// 生成走棋记录(中文记谱法)
function generateNotation(
piece: Piece,
from: Position,
to: Position,
captured?: Piece
): string {
const colNames = ["一", "二", "三", "四", "五", "六", "七", "八", "九"];
const colNamesBlack = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
const fromCol = piece.color === "red" ? colNames[8 - from.col] : colNamesBlack[from.col];
const toCol = piece.color === "red" ? colNames[8 - to.col] : colNamesBlack[to.col];
const direction = piece.color === "red"
? (from.row > to.row ? "进" : from.row < to.row ? "退" : "平")
: (from.row < to.row ? "进" : from.row > to.row ? "退" : "平");
const steps = Math.abs(to.row - from.row);
const stepStr = steps > 0 ? (piece.color === "red" ? colNames[steps - 1] : String(steps)) : "";
return `${piece.type}${fromCol}${direction}${stepStr || toCol}`;
}
// 获取所有可能的移动(用于AI)
export function getAllPossibleMoves(pieces: Piece[], color: Color): { piece: Piece; to: Position }[] {
const moves: { piece: Piece; to: Position }[] = [];
const colorPieces = pieces.filter((p) => p.alive && p.color === color);
for (const piece of colorPieces) {
const validMoves = getValidMoves(pieces, piece);
for (const to of validMoves) {
moves.push({ piece, to });
}
}
return moves;
}