303 lines
12 KiB
C#
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;
|
|
}
|
|
}
|