using System; using System.Collections.Generic; using UnityEngine; /// /// 象棋对局总管理(普通类,由 ChineseChessMainPanel 持有并统一控制) /// 负责:开局、回合、走棋校验与应用、将军/胜负判断、悔棋、网络同步数据封装 /// /// 联网方案:帧同步——走法与规则判断全在前端本地跑, /// 走子数据通过 SyncGameData 透传给对方(后端不解析),后端只负责胜负结算 /// public class ChessGameManager { /// 棋盘数据管理 public ChessBoardManager board = new ChessBoardManager(); /// 规则管理 public ChessRulesManager rules = new ChessRulesManager(); /// 悔棋记录管理 public ChessMoveRecorder recorder = new ChessMoveRecorder(); /// 本地玩家阵营 public ChessCamp myCamp; /// 当前轮到哪方走(红先黑后) public ChessCamp currentTurn; /// 已走的半回合数(同时作为帧序号) public int moveCount; /// 对局是否已结束 public bool isGameOver; /// 胜方 public ChessCamp winnerCamp; /// 走棋成功(本地或网络同步),参数含完整走子数据 public event Action OnMoveApplied; /// 悔棋完成,参数为回退的步数 public event Action OnUndoApplied; /// 对局结束(本地规则判定),参数为胜方 public event Action OnGameOver; /// 回合切换 public event Action OnTurnChanged; /// 现在是否轮到本地玩家 public bool IsMyTurn => !isGameOver && currentTurn == myCamp; /// 开一局新对局 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); } /// /// 本地玩家尝试走棋(完整校验:回合、归属、规则、送将),失败返回 false /// 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; } /// /// 应用网络同步过来的走棋(对方走的)。带帧序号校验,重复/乱序帧直接丢弃 /// 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; } /// 应用一步棋(数据层),并触发相关事件 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); } /// /// 悔棋:回退最近一步(一个半回合,双方确认后各调一次,两端回退同一手) /// 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; } /// /// 超时代走:AI 自动为本地玩家走一步合法棋(不判负、不结算) /// 策略:优先吃价值最高的子;没有吃子就避开会被攻击的位置随机走 /// 返回是否成功走棋 /// public bool AutoMove() { if (isGameOver || currentTurn != myCamp) return false; var candidates = new List(); int bestScore = int.MinValue; ChessCamp enemy = myCamp == ChessCamp.Red ? ChessCamp.Black : ChessCamp.Red; foreach (var p in board.allPieces) { if (!p.isAlive || p.camp != myCamp) continue; var legal = rules.GetLegalMoves(board, p); foreach (var to in legal) { int score; var target = board.GetPiece(to); if (target != null) { score = GetPieceValue(target.pieceType); // 吃子按价值 } else if (rules.IsSquareAttacked(board, to, enemy)) { score = -GetPieceValue(p.pieceType); // 走过去会被吃的位置降权 } else { score = 0; } if (score > bestScore) bestScore = score; candidates.Add(new AutoCandidate { from = p.pos, to = to, score = score }); } } if (candidates.Count == 0) return false; // 同分随机挑一个(避免每次 AI 走得一模一样) var pool = new List(); for (int i = 0; i < candidates.Count; i++) if (candidates[i].score == bestScore) pool.Add(candidates[i]); var pick = pool[UnityEngine.Random.Range(0, pool.Count)]; return TryLocalMove(pick.from, pick.to); } /// 棋子价值(AI 吃子/避损评估用) private static int GetPieceValue(ChessPieceType type) { switch (type) { case ChessPieceType.Jiang: return 1000; case ChessPieceType.Ju: return 90; // 车 case ChessPieceType.Pao: return 45; // 炮 case ChessPieceType.Ma: return 40; // 马 case ChessPieceType.Xiang: return 20; // 象 case ChessPieceType.Shi: return 20; // 士 case ChessPieceType.Bing: return 20; // 兵 default: return 0; } } /// AI 候选走法 private struct AutoCandidate { public Vector2Int from; public Vector2Int to; public int score; } private void SetGameOver(ChessCamp winner) { isGameOver = true; winnerCamp = winner; OnGameOver?.Invoke(winner); } /// /// 外部强制结束对局(挂起判负 / 服务端结算同步)。 /// silent=true 只置结束状态、不触发 OnGameOver(调用方自行上报和弹面板,避免重复) /// public void ForceGameOver(ChessCamp winner, bool silent = false) { if (isGameOver) return; isGameOver = true; winnerCamp = winner; if (!silent) OnGameOver?.Invoke(winner); } }