From 8ee9732a6fec69df00effb7b9df501a1ee6fb174 Mon Sep 17 00:00:00 2001 From: ZZY <2450266535@qq.com> Date: Thu, 7 Nov 2024 20:48:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(chinese-chess):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=B8=AD=E5=9B=BD=E8=B1=A1=E6=A3=8B=E6=A0=B8=E5=BF=83=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽象出 ChessCore 类,包含游戏初始化、行棋逻辑、悔棋等功能 - 重构 Player 类,优化行棋和记录逻辑 - 更新 ChessBoard 和 ChessPiece 类,适应新逻辑 - 移除冗余代码,提高代码可读性和可维护性 --- Scenes/Setting.tscn | 6 + Scripts/Controllers/ChessGame.cs | 98 +++++---------- Scripts/Entities/ChessBoard.cs | 37 +++--- Scripts/Entities/ChessPiece.cs | 8 +- Scripts/Src/ChineseChess/ChineseChess.cs | 154 +++++++++++++++++++++++ Scripts/Src/ChineseChess/Player.cs | 33 ++--- Scripts/Src/VirtualBoard.cs | 4 +- Scripts/Src/VirtualPiece.cs | 2 +- Scripts/Utilities/MoveRecords.cs | 4 + 9 files changed, 225 insertions(+), 121 deletions(-) diff --git a/Scenes/Setting.tscn b/Scenes/Setting.tscn index 46b5c9e..7510506 100644 --- a/Scenes/Setting.tscn +++ b/Scenes/Setting.tscn @@ -113,6 +113,7 @@ size_flags_horizontal = 3 [node name="Button" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer"] layout_mode = 2 size_flags_horizontal = 3 +disabled = true text = "Clear Config" [node name="MarginContainer2" type="MarginContainer" parent="BoxContainer/HBoxContainer"] @@ -121,6 +122,7 @@ size_flags_horizontal = 3 [node name="Button2" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer2"] layout_mode = 2 +disabled = true text = "Clear User Data" [node name="HFlowContainer" type="HFlowContainer" parent="BoxContainer"] @@ -129,21 +131,25 @@ layout_mode = 2 [node name="Button" type="Button" parent="BoxContainer/HFlowContainer"] layout_mode = 2 size_flags_horizontal = 3 +disabled = true text = "GetCacheDir" [node name="Button2" type="Button" parent="BoxContainer/HFlowContainer"] layout_mode = 2 size_flags_horizontal = 3 +disabled = true text = "GetConfigDir" [node name="Button3" type="Button" parent="BoxContainer/HFlowContainer"] layout_mode = 2 size_flags_horizontal = 3 +disabled = true text = "GetDataDir" [node name="Button4" type="Button" parent="BoxContainer/HFlowContainer"] layout_mode = 2 size_flags_horizontal = 3 +disabled = true text = "GetUserDataDir" [connection signal="pressed" from="BoxContainer/MarginContainer/Back" to="." method="OnBack"] diff --git a/Scripts/Controllers/ChessGame.cs b/Scripts/Controllers/ChessGame.cs index 0cfb266..b872a74 100644 --- a/Scripts/Controllers/ChessGame.cs +++ b/Scripts/Controllers/ChessGame.cs @@ -1,29 +1,27 @@ -using System.Collections; using Godot; using Godot.Collections; +using ChineseChess; public partial class ChessGame : Node2D { ChessBoard board; Global global; ConfirmationDialog dialog; private bool isSession = false; - - private Player playerSelf; - private Player playerOpponent; + ChessCore Game; + ChessCore.TurnsSideType sideSelf; + ChessCore.TurnsSideType sideOpposite; // Called when the node enters the scene tree for the first time. - public override void _Ready() - { + public override void _Ready() { // Init.Call(); global = GetNode("/root/Global"); board = GetNode("Chessboard"); isSession = global.RPClient.IsOnline(); - playerSelf = new Player(board.board, Player.PlayerType.Human); - playerOpponent = new Player(board.board, Player.PlayerType.Human); - InitChessBoard(); GetNode("Control/VBoxContainer/MarginContainer3/HFlowContainer/LineEdit") .Text = global.GlobalData["player_color"].AsString(); + LineEdit turnSideEdit = GetNode("Control/VBoxContainer/MarginContainer3/HFlowContainer/LineEdit2"); + turnSideEdit.Text = "red"; GD.PrintErr("ChessGame ", global.RPClient.GetUserId(), ":",global.GlobalData["player_color"]); dialog = new ConfirmationDialog { @@ -33,18 +31,37 @@ public partial class ChessGame : Node2D { }; AddChild(dialog); + if (global.GlobalData["player_color"].AsString() == "red") { + sideSelf = ChessCore.TurnsSideType.Red; + sideOpposite = ChessCore.TurnsSideType.Black; + } else { + sideSelf = ChessCore.TurnsSideType.Black; + sideOpposite = ChessCore.TurnsSideType.Red; + } + + if (isSession) { + Game = new(ChessCore.Mode.MultiMode, sideSelf); + } else { + Game = new(ChessCore.Mode.SingleMode, sideSelf); + } + board.LoadBoard(Game.board); + Game.InitGame(); + + Game.board.OnMove += (sender, e) => { + turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black"; + }; + board.OnPosClicked += (sender, pos) => { if (isSession) { + Game.OnPosClicked(pos, ChessCore.PlayerSideType.Self); var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary { {"type", "mouseClicked"}, {"X", pos.X}, {"Y", pos.Y}, {"id", global.RPClient.GetUserId()} }); - playerSelf.HandleBoardPosClick(pos); } else { - playerSelf.HandleBoardPosClick(pos); - playerSelf.SetAllowedPieces(null); + Game.OnPosClicked(pos); } }; @@ -65,44 +82,6 @@ public partial class ChessGame : Node2D { public override void _Process(double delta) { } - public void InitChessBoard() { - ArrayList black = InitializePieces("black", 0, new[] { - ("车", 0, 0), ("马", 1, 0), ("象", 2, 0), - ("士", 3, 0), ("将", 4, 0), ("士", 5, 0), - ("象", 6, 0), ("马", 7, 0), ("车", 8, 0), - ("炮", 1, 2), ("炮", 7, 2), - ("卒", 0, 3), ("卒", 2, 3), ("卒", 4, 3), ("卒", 6, 3), ("卒", 8, 3) - }); - - ArrayList red = InitializePieces("red", 9, new[] { - ("车", 0, -0), ("马", 1, -0), ("象", 2, -0), - ("士", 3, -0), ("将", 4, -0), ("士", 5, -0), - ("象", 6, -0), ("马", 7, -0), ("车", 8, -0), - ("炮", 1, -2), ("炮", 7, -2), - ("卒", 0, -3), ("卒", 2, -3), ("卒", 4, -3), ("卒", 6, -3), ("卒", 8, -3) - }); - if (global.GlobalData["player_color"].AsString() == "red") { - playerSelf.SetAllowedPieces(red); - playerOpponent.SetAllowedPieces(black); - } else { - playerSelf.SetAllowedPieces(black); - playerOpponent.SetAllowedPieces(red); - } - } - - private ArrayList InitializePieces(string color, int baseY, (string label, int x, int y)[] positions) { - ArrayList list = new(); - foreach (var (label, x, y) in positions) { - ChessPiece piece = new ChessPiece { - PieceLabel = label, - LabelColor = new Color(color) - }; - list.Add(piece.GetVirtualPiece()); - board.InsertNode(piece, new Vector2(x, baseY + y)); - } - return list; - } - private void SessionMsgHandle(Dictionary msg) { GD.PrintErr($"session msg: {msg}"); switch (msg["type"].AsString()) { @@ -120,25 +99,17 @@ public partial class ChessGame : Node2D { } Vector2 mouseClicked = new(GD.StrToVar(msg["X"].ToString()).AsInt32(), GD.StrToVar(msg["Y"].ToString()).AsInt32()); - playerOpponent.HandleBoardPosClick(mouseClicked); + Game.OnPosClicked(mouseClicked, ChessCore.PlayerSideType.Opponent); break; case "undo": - if (msg["id"].ToString() == global.RPClient.GetUserId()) { - break; - } - playerOpponent.Undo(); + Game.Undo(); break; case "reInit": - _ReInit(); + Game.ReInit(); break; } } - private void _ReInit() { - playerSelf.ReInit(); - InitChessBoard(); - } - private void BtnOver() { GD.Print($"BtnOver {isSession}"); if (isSession == false) { @@ -161,13 +132,12 @@ public partial class ChessGame : Node2D { GD.Print($"Undo {isSession}"); if (isSession) { - playerSelf.Undo(); global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{ {"type", "undo"}, {"id", global.RPClient.GetUserId()}, }); } else { - playerSelf.Undo(); + Game.Undo(); } } @@ -179,7 +149,7 @@ public partial class ChessGame : Node2D { {"type", "reInit"}, }); } else { - _ReInit(); + Game.ReInit(); } } } diff --git a/Scripts/Entities/ChessBoard.cs b/Scripts/Entities/ChessBoard.cs index e3e337b..5501b28 100644 --- a/Scripts/Entities/ChessBoard.cs +++ b/Scripts/Entities/ChessBoard.cs @@ -3,18 +3,13 @@ using System; using Godot; public partial class ChessBoard : Node2D { - public VirtualBoard board = null; - public Player playerSelf = null; - - public delegate bool ChessMoveFunc(Vector2 toPos, Vector2 fromPos); - // public Callable chessMoveFunc { get; set; } public event EventHandler OnMouseClicked; public event EventHandler OnPosClicked; public override void _Ready() { - board = new VirtualBoard(9, 10); - playerSelf = new Player(board); + } + public void LoadBoard(VirtualBoard board) { board.OnRemove += (sender, piece) => { if (piece.data != null) { RemoveChild(piece.data as Node); @@ -22,32 +17,28 @@ public partial class ChessBoard : Node2D { }; board.OnInsert += (sender, piece) => { - if (piece.data != null) { + if (piece.data != null && piece.data is Node) { AddChild(piece.data as Node); + } else { + ChessPiece chessPiece = new(piece.name, piece); + + if (piece.Pos().Y >= 5) { + chessPiece.LabelColor = new Color("red"); + } else { + chessPiece.LabelColor = new Color("black"); + } + AddChild(chessPiece); } }; - - // board.OnMove += (sender, args) => { - // // chessMoveFunc.Call(args.To, args.From); - // }; } public override void _Input(InputEvent @event) { if (@event is InputEventMouseButton mouseEvent && - mouseEvent.Pressed && - mouseEvent.ButtonIndex == MouseButton.Left) { - // HandleMouseClick(GetGlobalTransformWithCanvas().AffineInverse() * mouseButton.Position); - + mouseEvent.Pressed && + mouseEvent.ButtonIndex == MouseButton.Left) { OnMouseClicked?.Invoke(this, GetLocalMousePosition()); OnPosClicked?.Invoke(this, (PosTrans.transArrToPix.AffineInverse() * GetLocalMousePosition()).Round()); } } - - public void InsertNode(ChessPiece node, Vector2 arrayPos) { - AddChild(node); - VirtualPiece piece = node.GetVirtualPiece(); - // piece.Move(vector); - board.SetPiecePos(piece, arrayPos); - } } diff --git a/Scripts/Entities/ChessPiece.cs b/Scripts/Entities/ChessPiece.cs index 81b8128..03c2a2a 100644 --- a/Scripts/Entities/ChessPiece.cs +++ b/Scripts/Entities/ChessPiece.cs @@ -7,7 +7,7 @@ public partial class ChessPiece : Sprite2D { public string PieceLabel { get; set; } = null; // 文字颜色(可导出以编辑器调整) [Export] - public Color LabelColor { get; set; } = new Color("black"); + public Color LabelColor { get; set; } = new Color("white"); private Vector2 textureSize; private Label labelOfChessName; @@ -37,12 +37,12 @@ public partial class ChessPiece : Sprite2D { new Vector2(0, 0) ); - public ChessPiece() : this("", Vector2.Zero){ + public ChessPiece() : this("", new()){ } - public ChessPiece(string name, Vector2 pos) { + public ChessPiece(string name, VirtualPiece piece) { PieceLabel = name; - piece = new VirtualPiece(name, pos); + this.piece = piece; piece.OnMove += OnMove; piece.OnSelected += OnSelected; piece.data = this; diff --git a/Scripts/Src/ChineseChess/ChineseChess.cs b/Scripts/Src/ChineseChess/ChineseChess.cs index e69de29..37697a7 100644 --- a/Scripts/Src/ChineseChess/ChineseChess.cs +++ b/Scripts/Src/ChineseChess/ChineseChess.cs @@ -0,0 +1,154 @@ +using Vector2 = Godot.Vector2; +using System; +using System.Collections; +namespace ChineseChess; + +class ChessCore { + public enum Mode { + SingleMode, + MultiMode, + DebugMode, + }; + + public enum TurnsSideType { + Red, + Black, + }; + + public enum PlayerSideType { + Self, + Opponent, + Any, + }; + + private TurnsSideType sideType = TurnsSideType.Red; + private readonly TurnsSideType selfSide; + public readonly VirtualBoard board = new(9, 10); + private readonly Player playerSelf; + private readonly Player playerOpponent; + private readonly MoveRecords moveRecords; + public EventHandler OnMove; + + public ChessCore(Mode mode, TurnsSideType selfSide) { + this.selfSide = selfSide; + + playerSelf = new(board, Player.PlayerType.Human); + playerOpponent = new(board, Player.PlayerType.Human); + + playerSelf.OnMove += (sender, args) => { + moveRecords.AddRecord(board.GetPiece(args.To), board.GetPiece(args.From), + args.To, args.From); + }; + playerOpponent.OnMove += (sender, args) => { + moveRecords.AddRecord(board.GetPiece(args.To), board.GetPiece(args.From), + args.To, args.From); + }; + + moveRecords = new MoveRecords( + onAddRecordCallback: (newNode, oldNode, newPos, oldPos) => { + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + }, + onUndoRecordCallback: (newNode, oldNode, newPos, oldPos) => { + // GD.Print("Undo: ", newNode, "->", oldNode, ":", newPos, "->", oldPos); + VirtualPiece newPiece = newNode; + VirtualPiece oldPiece = oldNode; + board.MovePiece(newPos, oldPos); + if (newPiece != null) { + board.InsertPiece(newPiece, newPos); + } + }); + + switch (mode) { + case Mode.SingleMode: + break; + case Mode.MultiMode: + break; + default: + case Mode.DebugMode: + throw new NotImplementedException(); + } + + } + + public void InitGame() { + ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, new[] { + ("车", 0, 0), ("马", 1, 0), ("象", 2, 0), + ("士", 3, 0), ("将", 4, 0), ("士", 5, 0), + ("象", 6, 0), ("马", 7, 0), ("车", 8, 0), + ("炮", 1, 2), ("炮", 7, 2), + ("卒", 0, 3), ("卒", 2, 3), ("卒", 4, 3), ("卒", 6, 3), ("卒", 8, 3) + }); + + ArrayList redPart = InitOnePartPieces(TurnsSideType.Red, new[] { + ("车", 0, -0), ("马", 1, -0), ("象", 2, -0), + ("士", 3, -0), ("将", 4, -0), ("士", 5, -0), + ("象", 6, -0), ("马", 7, -0), ("车", 8, -0), + ("炮", 1, -2), ("炮", 7, -2), + ("卒", 0, -3), ("卒", 2, -3), ("卒", 4, -3), ("卒", 6, -3), ("卒", 8, -3) + }); + if (selfSide == TurnsSideType.Red) { + playerSelf.SetAllowedPieces(redPart); + playerOpponent.SetAllowedPieces(blackPart); + } else { + playerSelf.SetAllowedPieces(blackPart); + playerOpponent.SetAllowedPieces(redPart); + } + } + + private ArrayList InitOnePartPieces(TurnsSideType side, (string label, int x, int y)[] positions) { + ArrayList list = new(); + foreach (var (label, x, y) in positions) { + // FIXME: use a better way to initialize pieces + Vector2 pos = new(x, y + (TurnsSideType.Red == side ? 9 : 0)); + VirtualPiece piece = new(label, pos); + list.Add(piece); + board.InsertPiece(piece, pos); + } + return list; + } + + public void OnPosClicked(Vector2 pos, PlayerSideType clickedSide = PlayerSideType.Any) { + if (sideType == selfSide) { + playerSelf.CanMove = true; + playerOpponent.CanMove = false; + } else { + playerSelf.CanMove = false; + playerOpponent.CanMove = true; + } + + switch (clickedSide) { + case PlayerSideType.Any: + playerSelf.HandleBoardPosClick(pos); + playerOpponent.HandleBoardPosClick(pos); + break; + case PlayerSideType.Self: + playerSelf.HandleBoardPosClick(pos); + break; + case PlayerSideType.Opponent: + playerOpponent.HandleBoardPosClick(pos); + break; + } + + sideType = moveRecords.Count() % 2 == 0 ? TurnsSideType.Red : TurnsSideType.Black; + } + + public TurnsSideType GetTurnsType() { + sideType = moveRecords.Count() % 2 == 0 ? TurnsSideType.Red : TurnsSideType.Black; + return sideType; + } + + public void Undo() { + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + moveRecords.Undo(); + } + + public void ReInit() { + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + moveRecords.Clear(); + board.Clear(); + InitGame(); + } +} \ No newline at end of file diff --git a/Scripts/Src/ChineseChess/Player.cs b/Scripts/Src/ChineseChess/Player.cs index 1ca3156..d8ac1bf 100644 --- a/Scripts/Src/ChineseChess/Player.cs +++ b/Scripts/Src/ChineseChess/Player.cs @@ -5,9 +5,9 @@ using System.Collections; public class Player { private readonly VirtualBoard board; private readonly SelectedPiece selectedNode; - private readonly MoveRecords moveRecords; - public EventHandler OnMove; + public EventHandler OnMove; + public bool CanMove { get; set; } = true; public enum PlayerType { Human, @@ -18,15 +18,6 @@ public class Player { { this.board = board; this.selectedNode = new SelectedPiece(board); - this.moveRecords = new MoveRecords(onUndoRecordCallback: (newNode, oldNode, newPos, oldPos) => { - // GD.Print("Undo: ", newNode, "->", oldNode, ":", newPos, "->", oldPos); - VirtualPiece newPiece = newNode; - VirtualPiece oldPiece = oldNode; - this.board.MovePiece(newPos, oldPos); - if (newPiece != null) { - this.board.InsertPiece(newPiece, newPos); - } - }); } public void HandleBoardPosClick(Vector2 clickPos) { @@ -47,7 +38,7 @@ public class Player { } else { // Move piece // GD.Print("default MoveFunc Move: ", selectedNode.GetPos(), "->", clickPos); - MoveAndRecord(clickPos, selectedNode.GetPos()); + if (CanMove) MoveAndRecord(clickPos, selectedNode.GetPos()); } } @@ -56,6 +47,9 @@ public class Player { VirtualPiece toChess = board.GetPiece(toPos); VirtualPiece fromChess = board.GetPiece(fromPos); fromChess?.Selected(false); + + // MUST BE THERE !!! 防止删除节点后在启动回调导致错误 + OnMove?.Invoke(this, new VirtualBoard.MoveEventArgs { From = fromPos, To = toPos }); VirtualPiece NowNode; if (toChess != null) { @@ -64,26 +58,13 @@ public class Player { } else { NowNode = toChess; } - moveRecords.AddRecord(NowNode, fromChess, toPos, fromPos); - - OnMove?.Invoke(this, new VirtualBoard.MoveEventArgs { From = fromPos, To = toPos }); board.MovePiece(fromPos, toPos); selectedNode.Clear(); } - public void Undo() { - // ChessPiece selected = selectedNode.GetPiece(); - // selected?.DeSelected(); + public void SelectedClear() { selectedNode.Clear(); - moveRecords.Undo(); - } - - public void ReInit() { - moveRecords.Clear(); - board.Clear(); - selectedNode.Clear(); - // board.InitChessBoard(); } public void SetAllowedPieces(ArrayList allowedPieces) { diff --git a/Scripts/Src/VirtualBoard.cs b/Scripts/Src/VirtualBoard.cs index 30c82c1..08044c3 100644 --- a/Scripts/Src/VirtualBoard.cs +++ b/Scripts/Src/VirtualBoard.cs @@ -55,10 +55,9 @@ public class VirtualBoard { if (GetPiece(arrayPos) != null) { return false; } - OnInsert?.Invoke(this, piece); - SetPiecePos(piece, arrayPos); + SetPiecePos(piece, arrayPos); return true; } @@ -74,7 +73,6 @@ public class VirtualBoard { OnMove?.Invoke(this, new MoveEventArgs { From = from, To = to }); SetPiecePos(SetPiecePos(null, from), to); - return true; } diff --git a/Scripts/Src/VirtualPiece.cs b/Scripts/Src/VirtualPiece.cs index 829c89e..163ac6d 100644 --- a/Scripts/Src/VirtualPiece.cs +++ b/Scripts/Src/VirtualPiece.cs @@ -4,7 +4,7 @@ using System; public class VirtualPiece { private Vector2 pos; // 注意这个坐标的非像素坐标而是棋盘坐标 - private readonly string name; + public readonly string name; private bool isSelected; public object data; diff --git a/Scripts/Utilities/MoveRecords.cs b/Scripts/Utilities/MoveRecords.cs index 5eafbcf..602b330 100644 --- a/Scripts/Utilities/MoveRecords.cs +++ b/Scripts/Utilities/MoveRecords.cs @@ -31,6 +31,10 @@ public class MoveRecords { records.AddLast(record); // 将新记录加入队尾 } + public int Count() { + return records.Count; + } + public void Undo() { if (records.Count == 0) return; MoveRecord record = records.Last.Value; // 移除并获取队首的记录以执行撤销操作