Files
5.24BuYu/Assets/MyGame/aa/UI/Game/ChineseChess/ChessGame/ChessRulesManager.cs
T
2026-09-13 01:40:32 +08:00

303 lines
12 KiB
C#

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 走棋规则管理(普通类):
/// 各兵种走法生成、将军判断、将死/困毙判断、将帅照面判断
/// 前后端为帧同步方案:所有规则在两端前端各自跑同一套逻辑,后端不参与
/// </summary>
public class ChessRulesManager
{
private static readonly Vector2Int[] lineDirs =
{
new Vector2Int(1, 0), new Vector2Int(-1, 0),
new Vector2Int(0, 1), new Vector2Int(0, -1)
};
// 马的 8 个日字偏移
private static readonly int[,] maOffsets =
{
{ 1, 2 }, { 2, 1 }, { 2, -1 }, { 1, -2 },
{ -1, -2 }, { -2, -1 }, { -2, 1 }, { -1, 2 }
};
// 象的 4 个田字偏移
private static readonly int[,] xiangOffsets = { { 2, 2 }, { 2, -2 }, { -2, 2 }, { -2, -2 } };
// 士的 4 个斜向偏移
private static readonly int[,] shiOffsets = { { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
// 将帅的 4 个直向偏移
private static readonly int[,] jiangOffsets = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };
/// <summary>
/// 获取棋子在规则上的所有可走点(空位 + 吃子点),不含"送将"过滤
/// </summary>
public List<Vector2Int> GetRawMoves(ChessBoardManager board, ChessPieceData piece)
{
var result = new List<Vector2Int>();
if (piece == null || !piece.isAlive) return result;
int x = piece.pos.x;
int y = piece.pos.y;
switch (piece.pieceType)
{
case ChessPieceType.Ju: // 车:直线滑行
AddSlideMoves(board, piece, result);
break;
case ChessPieceType.Pao: // 炮:平移同车;吃子必须隔一个炮架
AddPaoMoves(board, piece, result);
break;
case ChessPieceType.Ma: // 马:日字 + 蹩马腿
for (int i = 0; i < maOffsets.GetLength(0); i++)
{
int dx = maOffsets[i, 0];
int dy = maOffsets[i, 1];
// 蹩马腿:马腿在走子方向上紧挨马的那一格
Vector2Int leg = new Vector2Int(x + dx / 2, y + dy / 2);
if (board.GetPiece(leg) != null) continue;
TryAddTarget(board, piece, new Vector2Int(x + dx, y + dy), result);
}
break;
case ChessPieceType.Xiang: // 象/相:田字 + 塞象眼 + 不过河
for (int i = 0; i < xiangOffsets.GetLength(0); i++)
{
int dx = xiangOffsets[i, 0];
int dy = xiangOffsets[i, 1];
Vector2Int eye = new Vector2Int(x + dx / 2, y + dy / 2); // 象眼
if (!ChessConst.InBoard(eye) || board.GetPiece(eye) != null) continue;
Vector2Int to = new Vector2Int(x + dx, y + dy);
if (!ChessConst.InBoard(to)) continue;
// 不能过河
if (piece.camp == ChessCamp.Red && to.y > ChessConst.RedSideMaxY) continue;
if (piece.camp == ChessCamp.Black && to.y < ChessConst.BlackSideMinY) continue;
TryAddTarget(board, piece, to, result);
}
break;
case ChessPieceType.Shi: // 士/仕:九宫内斜走一格
for (int i = 0; i < shiOffsets.GetLength(0); i++)
{
Vector2Int to = new Vector2Int(x + shiOffsets[i, 0], y + shiOffsets[i, 1]);
if (ChessConst.InPalace(to, piece.camp))
TryAddTarget(board, piece, to, result);
}
break;
case ChessPieceType.Jiang: // 将/帅:九宫内直走一格
for (int i = 0; i < jiangOffsets.GetLength(0); i++)
{
Vector2Int to = new Vector2Int(x + jiangOffsets[i, 0], y + jiangOffsets[i, 1]);
if (ChessConst.InPalace(to, piece.camp))
TryAddTarget(board, piece, to, result);
}
break;
case ChessPieceType.Bing: // 兵/卒:前进一步;过河后可左右平移
int fwd = piece.camp == ChessCamp.Red ? 1 : -1;
TryAddTarget(board, piece, new Vector2Int(x, y + fwd), result);
bool crossed = piece.camp == ChessCamp.Red
? y >= ChessConst.BlackSideMinY
: y <= ChessConst.RedSideMaxY;
if (crossed)
{
TryAddTarget(board, piece, new Vector2Int(x - 1, y), result);
TryAddTarget(board, piece, new Vector2Int(x + 1, y), result);
}
break;
}
return result;
}
/// <summary>
/// 获取合法走法:在 RawMoves 基础上剔除"走完后自己被将军 / 将帅照面"的点
/// </summary>
public List<Vector2Int> GetLegalMoves(ChessBoardManager board, ChessPieceData piece)
{
var result = new List<Vector2Int>();
var raw = GetRawMoves(board, piece);
for (int i = 0; i < raw.Count; i++)
{
if (IsMoveSafe(board, piece, raw[i])) result.Add(raw[i]);
}
return result;
}
/// <summary>模拟走一步后自己是否安全(不被将军、不与对方将帅照面)</summary>
public bool IsMoveSafe(ChessBoardManager board, ChessPieceData piece, Vector2Int to)
{
Vector2Int from = piece.pos;
ChessPieceData captured = board.ApplyMove(piece, to);
bool safe = !IsInCheck(board, piece.camp);
board.UndoMove(piece, from, captured);
return safe;
}
/// <summary>camp 方的将/帅是否正被将军(含将帅照面)</summary>
public bool IsInCheck(ChessBoardManager board, ChessCamp camp)
{
var king = board.FindKing(camp);
if (king == null) return false;
return IsSquareAttacked(board, king.pos, camp == ChessCamp.Red ? ChessCamp.Black : ChessCamp.Red);
}
/// <summary>某点是否被 byCamp 方攻击(将帅照面按互相攻击处理)</summary>
public bool IsSquareAttacked(ChessBoardManager board, Vector2Int target, ChessCamp byCamp)
{
var pieces = board.allPieces;
for (int i = 0; i < pieces.Count; i++)
{
var p = pieces[i];
if (!p.isAlive || p.camp != byCamp) continue;
int dx = target.x - p.pos.x;
int dy = target.y - p.pos.y;
switch (p.pieceType)
{
case ChessPieceType.Ju: // 车
if ((dx == 0 || dy == 0) && CountPiecesBetween(board, p.pos, target) == 0) return true;
break;
case ChessPieceType.Pao: // 炮:隔一个炮架打
if ((dx == 0 || dy == 0) && CountPiecesBetween(board, p.pos, target) == 1) return true;
break;
case ChessPieceType.Ma: // 马(注意马腿在马的一侧)
if (Mathf.Abs(dx * dy) == 2)
{
Vector2Int leg = p.pos + new Vector2Int(dx / 2, dy / 2);
if (board.GetPiece(leg) == null) return true;
}
break;
case ChessPieceType.Bing: // 兵/卒只攻击前方(过河后加左右)
if (p.camp == ChessCamp.Red)
{
if (dx == 0 && dy == 1) return true;
bool crossed = p.pos.y >= ChessConst.BlackSideMinY;
if (crossed && dy == 0 && Mathf.Abs(dx) == 1) return true;
}
else
{
if (dx == 0 && dy == -1) return true;
bool crossed = p.pos.y <= ChessConst.RedSideMaxY;
if (crossed && dy == 0 && Mathf.Abs(dx) == 1) return true;
}
break;
case ChessPieceType.Jiang: // 将帅照面:同列且中间无子
if (dx == 0 && CountPiecesBetween(board, p.pos, target) == 0) return true;
break;
case ChessPieceType.Xiang: // 象
if (Mathf.Abs(dx) == 2 && Mathf.Abs(dy) == 2)
{
Vector2Int eye = p.pos + new Vector2Int(dx / 2, dy / 2);
if (board.GetPiece(eye) == null) return true;
}
break;
case ChessPieceType.Shi: // 士
if (Mathf.Abs(dx) == 1 && Mathf.Abs(dy) == 1) return true;
break;
}
}
return false;
}
/// <summary>camp 方是否还有任何一步合法走法(没有 → 被将死或困毙)</summary>
public bool HasAnyLegalMove(ChessBoardManager board, ChessCamp camp)
{
var pieces = board.allPieces;
for (int i = 0; i < pieces.Count; i++)
{
var p = pieces[i];
if (!p.isAlive || p.camp != camp) continue;
var raw = GetRawMoves(board, p);
for (int j = 0; j < raw.Count; j++)
{
if (IsMoveSafe(board, p, raw[j])) return true;
}
}
return false;
}
/// <summary>目标格是空位或是敌方棋子时可走</summary>
private void TryAddTarget(ChessBoardManager board, ChessPieceData piece, Vector2Int to, List<Vector2Int> result)
{
if (!ChessConst.InBoard(to)) return;
var occ = board.GetPiece(to);
if (occ == null || occ.camp != piece.camp) result.Add(to);
}
/// <summary>车:四个方向滑行</summary>
private void AddSlideMoves(ChessBoardManager board, ChessPieceData piece, List<Vector2Int> result)
{
for (int d = 0; d < lineDirs.Length; d++)
{
Vector2Int cur = piece.pos + lineDirs[d];
while (ChessConst.InBoard(cur) && board.GetPiece(cur) == null)
{
result.Add(cur);
cur += lineDirs[d];
}
// 撞到的第一个子若是敌方 → 可吃
if (ChessConst.InBoard(cur))
{
var occ = board.GetPiece(cur);
if (occ != null && occ.camp != piece.camp) result.Add(cur);
}
}
}
/// <summary>炮:平移同车;吃子需要隔恰好一个炮架</summary>
private void AddPaoMoves(ChessBoardManager board, ChessPieceData piece, List<Vector2Int> result)
{
for (int d = 0; d < lineDirs.Length; d++)
{
Vector2Int dir = lineDirs[d];
Vector2Int cur = piece.pos + dir;
// 先平移到第一个子(炮架)之前
while (ChessConst.InBoard(cur) && board.GetPiece(cur) == null)
{
result.Add(cur);
cur += dir;
}
if (!ChessConst.InBoard(cur)) continue;
// 越过炮架继续找第一个子
cur += dir;
while (ChessConst.InBoard(cur) && board.GetPiece(cur) == null)
cur += dir;
if (ChessConst.InBoard(cur))
{
var occ = board.GetPiece(cur);
if (occ != null && occ.camp != piece.camp) result.Add(cur);
}
}
}
/// <summary>统计同一直线上两点之间(开区间)的棋子数,要求两点同行或同列</summary>
private int CountPiecesBetween(ChessBoardManager board, Vector2Int a, Vector2Int b)
{
int count = 0;
if (a.x == b.x)
{
int dir = b.y > a.y ? 1 : -1;
for (int y = a.y + dir; y != b.y; y += dir)
if (board.GetPiece(a.x, y) != null) count++;
}
else if (a.y == b.y)
{
int dir = b.x > a.x ? 1 : -1;
for (int x = a.x + dir; x != b.x; x += dir)
if (board.GetPiece(x, a.y) != null) count++;
}
return count;
}
}