171 lines
5.9 KiB
C#
171 lines
5.9 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 象棋对局总管理(普通类,由 ChineseChessMainPanel 持有并统一控制)
|
|
/// 负责:开局、回合、走棋校验与应用、将军/胜负判断、悔棋、网络同步数据封装
|
|
///
|
|
/// 联网方案:帧同步——走法与规则判断全在前端本地跑,
|
|
/// 走子数据通过 SyncGameData 透传给对方(后端不解析),后端只负责胜负结算
|
|
/// </summary>
|
|
public class ChessGameManager
|
|
{
|
|
/// <summary>棋盘数据管理</summary>
|
|
public ChessBoardManager board = new ChessBoardManager();
|
|
|
|
/// <summary>规则管理</summary>
|
|
public ChessRulesManager rules = new ChessRulesManager();
|
|
|
|
/// <summary>悔棋记录管理</summary>
|
|
public ChessMoveRecorder recorder = new ChessMoveRecorder();
|
|
|
|
/// <summary>本地玩家阵营</summary>
|
|
public ChessCamp myCamp;
|
|
|
|
/// <summary>当前轮到哪方走(红先黑后)</summary>
|
|
public ChessCamp currentTurn;
|
|
|
|
/// <summary>已走的半回合数(同时作为帧序号)</summary>
|
|
public int moveCount;
|
|
|
|
/// <summary>对局是否已结束</summary>
|
|
public bool isGameOver;
|
|
|
|
/// <summary>胜方</summary>
|
|
public ChessCamp winnerCamp;
|
|
|
|
/// <summary>走棋成功(本地或网络同步),参数含完整走子数据</summary>
|
|
public event Action<ChessMoveEventArgs> OnMoveApplied;
|
|
|
|
/// <summary>悔棋完成,参数为回退的步数</summary>
|
|
public event Action<int> OnUndoApplied;
|
|
|
|
/// <summary>对局结束(本地规则判定),参数为胜方</summary>
|
|
public event Action<ChessCamp> OnGameOver;
|
|
|
|
/// <summary>回合切换</summary>
|
|
public event Action<ChessCamp> OnTurnChanged;
|
|
|
|
/// <summary>现在是否轮到本地玩家</summary>
|
|
public bool IsMyTurn => !isGameOver && currentTurn == myCamp;
|
|
|
|
/// <summary>开一局新对局</summary>
|
|
public void StartGame(ChessCamp myCamp)
|
|
{
|
|
this.myCamp = myCamp;
|
|
board.InitDefaultBoard();
|
|
recorder.Clear();
|
|
moveCount = 0;
|
|
isGameOver = false;
|
|
winnerCamp = ChessCamp.None;
|
|
currentTurn = ChessCamp.Red; // 红先
|
|
OnTurnChanged?.Invoke(currentTurn);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 本地玩家尝试走棋(完整校验:回合、归属、规则、送将),失败返回 false
|
|
/// </summary>
|
|
public bool TryLocalMove(Vector2Int from, Vector2Int to)
|
|
{
|
|
if (isGameOver || currentTurn != myCamp) return false;
|
|
var piece = board.GetPiece(from);
|
|
if (piece == null || piece.camp != myCamp) return false;
|
|
if (!rules.GetLegalMoves(board, piece).Contains(to)) return false;
|
|
ApplyMoveInternal(piece, to, true);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用网络同步过来的走棋(对方走的)。带帧序号校验,重复/乱序帧直接丢弃
|
|
/// </summary>
|
|
public bool ApplyRemoteMove(ChessMoveData move)
|
|
{
|
|
if (isGameOver || move == null) return false;
|
|
|
|
// 帧序号校验
|
|
if (move.seq < moveCount) return false; // 重复帧
|
|
if (move.seq > moveCount)
|
|
{
|
|
Debug.LogWarning($"[Chess] 丢帧:收到 seq={move.seq},本地 moveCount={moveCount}");
|
|
return false; // 乱序帧
|
|
}
|
|
|
|
var from = new Vector2Int(move.fromX, move.fromY);
|
|
var to = new Vector2Int(move.toX, move.toY);
|
|
var piece = board.GetPiece(from);
|
|
if (piece == null || piece.camp == myCamp || piece.camp != currentTurn) return false;
|
|
if (!rules.GetLegalMoves(board, piece).Contains(to)) return false;
|
|
|
|
ApplyMoveInternal(piece, to, false);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>应用一步棋(数据层),并触发相关事件</summary>
|
|
private void ApplyMoveInternal(ChessPieceData piece, Vector2Int to, bool isLocal)
|
|
{
|
|
Vector2Int from = piece.pos;
|
|
ChessPieceData captured = board.ApplyMove(piece, to);
|
|
moveCount++;
|
|
recorder.Push(new ChessMoveRecord { piece = piece, from = from, to = to, captured = captured });
|
|
|
|
ChessCamp moverCamp = piece.camp;
|
|
currentTurn = moverCamp == ChessCamp.Red ? ChessCamp.Black : ChessCamp.Red;
|
|
|
|
// 胜负与将军判断(纯前端本地判定)
|
|
bool check = false;
|
|
bool gameOver = false;
|
|
if (captured != null && captured.pieceType == ChessPieceType.Jiang)
|
|
{
|
|
gameOver = true; // 将/帅被吃
|
|
}
|
|
else
|
|
{
|
|
check = rules.IsInCheck(board, currentTurn);
|
|
if (!rules.HasAnyLegalMove(board, currentTurn))
|
|
{
|
|
gameOver = true; // 对方无任何合法走法 → 将死或困毙
|
|
}
|
|
}
|
|
|
|
var args = new ChessMoveEventArgs
|
|
{
|
|
move = new ChessMoveData { seq = moveCount - 1, fromX = from.x, fromY = from.y, toX = to.x, toY = to.y },
|
|
movePieceId = piece.id,
|
|
capturedPieceId = captured != null ? captured.id : -1,
|
|
isLocal = isLocal,
|
|
isCheck = check,
|
|
moverCamp = moverCamp
|
|
};
|
|
OnMoveApplied?.Invoke(args);
|
|
|
|
if (gameOver) SetGameOver(moverCamp);
|
|
else OnTurnChanged?.Invoke(currentTurn);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 悔棋:回退最近一步(一个半回合,双方确认后各调一次,两端回退同一手)
|
|
/// </summary>
|
|
public bool UndoOneStep()
|
|
{
|
|
var r = recorder.PopLast();
|
|
if (r == null) return false;
|
|
|
|
board.UndoMove(r.piece, r.from, r.captured);
|
|
currentTurn = r.piece.camp;
|
|
moveCount--;
|
|
isGameOver = false;
|
|
winnerCamp = ChessCamp.None;
|
|
|
|
OnUndoApplied?.Invoke(1);
|
|
OnTurnChanged?.Invoke(currentTurn);
|
|
return true;
|
|
}
|
|
|
|
private void SetGameOver(ChessCamp winner)
|
|
{
|
|
isGameOver = true;
|
|
winnerCamp = winner;
|
|
OnGameOver?.Invoke(winner);
|
|
}
|
|
}
|