From 9c619784af34af3a7a22280dd53a6066545915a7 Mon Sep 17 00:00:00 2001 From: ZZY <2450266535@qq.com> Date: Fri, 22 Nov 2024 20:01:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=87=8D=E6=9E=84):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=A3=8B=E7=9B=98=E5=92=8C=E6=A3=8B=E5=AD=90=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构了 ChessBoard 和 ChessPiece 类的逻辑 - 添加了新版本的 ChineseChess 库 - 删除了旧版本的 VirtualBoard 和 VirtualPiece 类 - 更新了相关的场景和脚本 --- Scenes/Entities/Chessboard.tscn | 6 +- Scenes/Setting.tscn | 7 + Scripts/Controllers/ChessGame.cs | 16 +- Scripts/Entities/ChessBoard.cs | 61 +++-- Scripts/Entities/ChessPiece.cs | 50 ++-- Scripts/Src/AbstractBoard.cs | 140 ++++++++++ Scripts/Src/AbstractPiece.cs | 73 ++++++ Scripts/Src/ChineseChess/CCBoard.cs | 100 +++++++ Scripts/Src/ChineseChess/CCMain.cs | 107 ++++++++ Scripts/Src/ChineseChess/CCPiece.cs | 81 ++++++ .../ChineseChess/{Player.cs => CCPlayer.cs} | 71 ++--- Scripts/Src/ChineseChess/CCTypes.cs | 246 ++++++++++++++++++ Scripts/Src/ChineseChess/ChessAdvisor.cs | 0 Scripts/Src/ChineseChess/ChessCannon.cs | 0 Scripts/Src/ChineseChess/ChessChariot.cs | 0 Scripts/Src/ChineseChess/ChessElephant.cs | 0 Scripts/Src/ChineseChess/ChessGeneral.cs | 0 Scripts/Src/ChineseChess/ChessHorse.cs | 0 Scripts/Src/ChineseChess/ChessPawn.cs | 0 Scripts/Src/ChineseChess/ChessTypes.cs | 20 -- Scripts/Src/ChineseChess/ChineseChess.cs | 154 ----------- Scripts/Src/IBoard.cs | 32 +++ Scripts/Src/IPiece.cs | 18 ++ Scripts/Src/Vector.cs | 133 ++++++++++ Scripts/Src/VirtualBoard.cs | 97 ------- Scripts/Src/VirtualPiece.cs | 38 --- Scripts/Utilities/MoveRecords.cs | 63 ----- Scripts/Utilities/Transforms.cs | 10 - 28 files changed, 1068 insertions(+), 455 deletions(-) create mode 100644 Scripts/Src/AbstractBoard.cs create mode 100644 Scripts/Src/AbstractPiece.cs create mode 100644 Scripts/Src/ChineseChess/CCBoard.cs create mode 100644 Scripts/Src/ChineseChess/CCMain.cs create mode 100644 Scripts/Src/ChineseChess/CCPiece.cs rename Scripts/Src/ChineseChess/{Player.cs => CCPlayer.cs} (54%) create mode 100644 Scripts/Src/ChineseChess/CCTypes.cs delete mode 100644 Scripts/Src/ChineseChess/ChessAdvisor.cs delete mode 100644 Scripts/Src/ChineseChess/ChessCannon.cs delete mode 100644 Scripts/Src/ChineseChess/ChessChariot.cs delete mode 100644 Scripts/Src/ChineseChess/ChessElephant.cs delete mode 100644 Scripts/Src/ChineseChess/ChessGeneral.cs delete mode 100644 Scripts/Src/ChineseChess/ChessHorse.cs delete mode 100644 Scripts/Src/ChineseChess/ChessPawn.cs delete mode 100644 Scripts/Src/ChineseChess/ChessTypes.cs delete mode 100644 Scripts/Src/ChineseChess/ChineseChess.cs create mode 100644 Scripts/Src/IBoard.cs create mode 100644 Scripts/Src/IPiece.cs create mode 100644 Scripts/Src/Vector.cs delete mode 100644 Scripts/Src/VirtualBoard.cs delete mode 100644 Scripts/Src/VirtualPiece.cs delete mode 100644 Scripts/Utilities/MoveRecords.cs delete mode 100644 Scripts/Utilities/Transforms.cs diff --git a/Scenes/Entities/Chessboard.tscn b/Scenes/Entities/Chessboard.tscn index 6a9bcbc..1b9b35c 100644 --- a/Scenes/Entities/Chessboard.tscn +++ b/Scenes/Entities/Chessboard.tscn @@ -12,7 +12,11 @@ visible = false script = ExtResource("3_26g24") [node name="Layer0" type="TileMapLayer" parent="."] -use_parent_material = true +show_behind_parent = true position = Vector2(-8, 184) tile_map_data = PackedByteArray("AAAAAPv/JAAEAAEAAAD+//v/JAABAAIAAAACAPv/JAABAAIAABAAAPn/JAABAAIAAFD+//n/JAABAAEAAAACAPn/JAABAAEAAFAAAP3/JAAEAAAAACD+//3/JAACAAEAACACAP3/JAACAAEAADAEAP3/JAADAAAAACAGAP3/JAADAAAAACAIAP3/JAAAAAEAACD8//3/JAADAAAAACD6//3/JAADAAAAACD4//3/JAAAAAEAADAEAPv/JAACAAAAAAAGAPv/JAACAAAAAAAEAPn/JAACAAAAAAD8//v/JAACAAAAAAD8//n/JAACAAAAAAD6//v/JAACAAAAAAD6//f/JAACAAAAAAD+//f/JAACAAAAAAACAPf/JAACAAAAAAAGAPf/JAACAAAAAAAAAPX/JAADAAAAAAACAPX/JAADAAAAAAAEAPX/JAADAAAAAAAGAPX/JAADAAAAAAD+//X/JAADAAAAAAD8//X/JAADAAAAAAD6//X/JAADAAAAAAAIAPn/JAADAAAAAFD4//v/JAADAAAAAED4//n/JAADAAAAAED4//X/JAAAAAEAAGAIAPX/JAAAAAEAAHD6//n/JAADAAEAAAAGAPn/JAADAAEAAAAIAPv/JAADAAAAAFD8//f/JAADAAEAAAAAAPf/JAADAAEAAAAEAPf/JAADAAEAAAAIAPf/JQAAAAAAAFD4//f/JQAAAAAAAED4//P/JAAAAAEAAED6//P/JAADAAAAACD8//P/JAADAAAAACD+//P/JAADAAAAACAAAPP/JAADAAAAACACAPP/JAADAAAAACAEAPP/JAADAAAAACAGAPP/JAADAAAAACAIAPP/JAAAAAEAAFD4//H/JQAAAAAAAGD6//H/JAACAAAAACD8//H/JAADAAEAACD+//H/JAACAAAAACAAAPH/JAADAAEAACACAPH/JAACAAAAACAEAPH/JAADAAEAACAGAPH/JAACAAAAACAIAPH/JQAAAAAAAHD4/+//JAADAAAAAGD6/+//JAADAAEAACD8/+//JAACAAAAACD+/+//JAABAAEAACAAAO//JAABAAIAAHACAO//JAABAAEAAHAEAO//JAACAAAAACAGAO//JAADAAEAACAIAO//JAADAAAAAHD4/+3/JAADAAAAAGD6/+3/JAACAAAAACD8/+3/JAACAAAAACD+/+3/JAABAAIAACAAAO3/JAAEAAEAACACAO3/JAABAAIAADAEAO3/JAACAAAAACAGAO3/JAACAAAAACAIAO3/JAADAAAAAHD4/+v/JAAAAAEAABD6/+v/JAADAAAAAAD8/+v/JAADAAAAAAD+/+v/JAACAAEAAAAAAOv/JAAEAAAAAAACAOv/JAACAAEAABAEAOv/JAADAAAAAAAGAOv/JAADAAAAAAAIAOv/JAAAAAEAAAA=") tile_set = ExtResource("1_ws3cq") +collision_enabled = false +collision_visibility_mode = 2 +navigation_enabled = false +navigation_visibility_mode = 2 diff --git a/Scenes/Setting.tscn b/Scenes/Setting.tscn index 7510506..d4ab2c2 100644 --- a/Scenes/Setting.tscn +++ b/Scenes/Setting.tscn @@ -152,6 +152,13 @@ size_flags_horizontal = 3 disabled = true text = "GetUserDataDir" +[node name="MarginContainer8" type="MarginContainer" parent="BoxContainer"] +layout_mode = 2 + +[node name="ColorPickerButton" type="ColorPickerButton" parent="BoxContainer/MarginContainer8"] +layout_mode = 2 +text = "Trace Color" + [connection signal="pressed" from="BoxContainer/MarginContainer/Back" to="." method="OnBack"] [connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"] [connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit" to="." method="OnServerUrlChanged"] diff --git a/Scripts/Controllers/ChessGame.cs b/Scripts/Controllers/ChessGame.cs index b872a74..b6de100 100644 --- a/Scripts/Controllers/ChessGame.cs +++ b/Scripts/Controllers/ChessGame.cs @@ -45,15 +45,18 @@ public partial class ChessGame : Node2D { Game = new(ChessCore.Mode.SingleMode, sideSelf); } board.LoadBoard(Game.board); - Game.InitGame(); + Game.Init(); - Game.board.OnMove += (sender, e) => { + Game.board.OnStepsChanged += (sender, e) => { turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black"; }; board.OnPosClicked += (sender, pos) => { + int posX = (int)pos.X; + int posY = (int)pos.Y; + Vector.Vector2I vector = new(posX, posY); if (isSession) { - Game.OnPosClicked(pos, ChessCore.PlayerSideType.Self); + Game.OnPosClicked(vector, ChessCore.PlayerSideType.Self); var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary { {"type", "mouseClicked"}, {"X", pos.X}, @@ -61,7 +64,7 @@ public partial class ChessGame : Node2D { {"id", global.RPClient.GetUserId()} }); } else { - Game.OnPosClicked(pos); + Game.OnPosClicked(vector); } }; @@ -99,7 +102,8 @@ public partial class ChessGame : Node2D { } Vector2 mouseClicked = new(GD.StrToVar(msg["X"].ToString()).AsInt32(), GD.StrToVar(msg["Y"].ToString()).AsInt32()); - Game.OnPosClicked(mouseClicked, ChessCore.PlayerSideType.Opponent); + Vector.Vector2I vector = new((int)mouseClicked.X, (int)mouseClicked.Y); + Game.OnPosClicked(vector, ChessCore.PlayerSideType.Opponent); break; case "undo": Game.Undo(); @@ -137,6 +141,7 @@ public partial class ChessGame : Node2D { {"id", global.RPClient.GetUserId()}, }); } else { + if (Game.board.Steps == 0) board.Clear(); Game.Undo(); } } @@ -150,6 +155,7 @@ public partial class ChessGame : Node2D { }); } else { Game.ReInit(); + board.Clear(); } } } diff --git a/Scripts/Entities/ChessBoard.cs b/Scripts/Entities/ChessBoard.cs index 5501b28..83f8219 100644 --- a/Scripts/Entities/ChessBoard.cs +++ b/Scripts/Entities/ChessBoard.cs @@ -1,38 +1,60 @@ // Chessboard.cs using System; using Godot; +using ChineseChess; +using Godot.Collections; public partial class ChessBoard : Node2D { public event EventHandler OnMouseClicked; public event EventHandler OnPosClicked; + public Vector2 from = Vector2.Inf, to = Vector2.Inf; + public Array canMovePos = new(); + public override void _Ready() { } - public void LoadBoard(VirtualBoard board) { + public void Clear() { + from = to = Vector2.Inf; + QueueRedraw(); + } + + public void LoadBoard(CCBoard board) { board.OnRemove += (sender, piece) => { - if (piece.data != null) { - RemoveChild(piece.data as Node); + if (piece?.Data.TryGetValue("Godot", out object node) ?? false) { + RemoveChild(node as Node); } }; board.OnInsert += (sender, piece) => { - if (piece.data != null && piece.data is Node) { - AddChild(piece.data as Node); + ChessPiece chessPiece = null; + if (piece.Data.TryGetValue("Godot", out object node)) { + chessPiece = node as ChessPiece; } 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); + chessPiece = new((CCPiece)piece); } + chessPiece.ShowBehindParent = true; + AddChild(chessPiece); + }; + + board.OnMove += (sender, vals) => { + from = PosTrans.transArrToPix * new Vector2(vals.From.X, vals.From.Y); + to = PosTrans.transArrToPix * new Vector2(vals.To.X, vals.To.Y); + QueueRedraw(); }; } - - public override void _Input(InputEvent @event) { + + public override void _Draw() { + Color color = new(0.7f, 0.7f, 0.7f, 0.5f); + DrawLine(from, to, color, PosTrans.pixGripSize / 5); + DrawCircle(from, PosTrans.pixGripSize / 2.2f, color); + foreach (Vector2 pos in canMovePos) { + DrawCircle(pos, PosTrans.pixGripSize / 2.2f, + color.Inverted()); + } + } + + public override void _Input(InputEvent @event) { if (@event is InputEventMouseButton mouseEvent && mouseEvent.Pressed && mouseEvent.ButtonIndex == MouseButton.Left) { @@ -41,4 +63,13 @@ public partial class ChessBoard : Node2D { GetLocalMousePosition()).Round()); } } + + public static class PosTrans { + public static readonly int pixGripSize = 32; + public static readonly Transform2D transArrToPix = new( + new Vector2(pixGripSize, 0), + new Vector2(0, pixGripSize), + new Vector2(-4, -4.5f) * pixGripSize + ); + } } diff --git a/Scripts/Entities/ChessPiece.cs b/Scripts/Entities/ChessPiece.cs index 03c2a2a..8b18863 100644 --- a/Scripts/Entities/ChessPiece.cs +++ b/Scripts/Entities/ChessPiece.cs @@ -1,34 +1,46 @@ // Chesspiece.cs using Godot; +using ChineseChess; +using System.Linq; +using System; public partial class ChessPiece : Sprite2D { - // 文字内容 [Export] public string PieceLabel { get; set; } = null; - // 文字颜色(可导出以编辑器调整) + // Text Color (Can Export for Editor Adjust) [Export] public Color LabelColor { get; set; } = new Color("white"); private Vector2 textureSize; private Label labelOfChessName; - private readonly VirtualPiece piece; + private readonly IPiece piece; - public VirtualPiece GetVirtualPiece() { + public IPiece GetVirtualPiece() { return piece; } - private void OnMove(Vector2 newPos) { - Position = PosTrans.transArrToPix * new Vector2(newPos.X, newPos.Y); + private void OnPos(object _self, Vector.Vector2I oldPos) { + CCPiece self = (CCPiece)_self; + Position = ChessBoard.PosTrans.transArrToPix * new Vector2(self.Pos.X, self.Pos.Y); } - public void OnSelected(bool isSelected) { - if (isSelected) { - GD.Print($"{piece.Pos()} is selected"); + public void OnSelected(object _self, bool oldIsSelected) { + CCPiece self = (CCPiece)_self; + ChessBoard chessBoard = GetParent() as ChessBoard; + if (self.IsSelected) { + GD.Print($"{piece.Pos} is selected"); Transform *= transToSeleted; + foreach (var item in self.CanMoveAllPos()) { + chessBoard.canMovePos.Add(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y)); + } } else { - GD.Print($"{piece.Pos()} is deselected"); + GD.Print($"{piece.Pos} is deselected"); Transform *= transToSeleted.AffineInverse(); + foreach (var item in self.CanMoveAllPos()) { + chessBoard.canMovePos.Remove(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y)); + } } + chessBoard.QueueRedraw(); } Transform2D transToSeleted = new( @@ -37,24 +49,26 @@ public partial class ChessPiece : Sprite2D { new Vector2(0, 0) ); - public ChessPiece() : this("", new()){ + public ChessPiece() : this(new CCPiece()){ } - public ChessPiece(string name, VirtualPiece piece) { - PieceLabel = name; + public ChessPiece(CCPiece piece) { + PieceLabel = piece.CNName; this.piece = piece; - piece.OnMove += OnMove; + LabelColor = piece.TurnsSide == ChessCore.TurnsSideType.Red ? new Color("red") : new Color("black"); + piece.OnPos += OnPos; piece.OnSelected += OnSelected; - piece.data = this; + // Must Be Call for init Pos + OnPos(piece, piece.Pos); + piece.Data.TryAdd("Godot", this); } - // Called when the node enters the scene tree for the first time. - public override void _Ready() { + // Called when the node enters the scene tree for the first time. + public override void _Ready() { InitLabel(); } private void InitLabel() { - // this.Texture.ResourcePath = "res://Asserts/ChesspieceBase.tres"; Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres"); textureSize = Texture.GetSize(); Vector2 labalPosition = new( diff --git a/Scripts/Src/AbstractBoard.cs b/Scripts/Src/AbstractBoard.cs new file mode 100644 index 0000000..f5c19d5 --- /dev/null +++ b/Scripts/Src/AbstractBoard.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using Vector2I = Vector.Vector2I; + +using static IBoard; +public abstract class AbstractBoard : IBoard { + private readonly int rows; + private readonly int cols; + protected readonly IPiece[,] pieces; + protected readonly List moveRecords = new(); + protected readonly int MAX_RECORDS; + + public int Rows => rows; + public int Cols => cols; + + public event EventHandler OnSetPiece; + public event EventHandler OnInsert; + public event EventHandler OnRemove; + public event EventHandler OnMove; + + public event EventHandler OnAddRecord; + public event EventHandler OnUndoRecord; + + public AbstractBoard(int rows, int cols, int maxRecords = int.MaxValue) { + this.rows = rows; + this.cols = cols; + pieces = new IPiece[rows, cols]; + MAX_RECORDS = maxRecords; + } + + public virtual bool IsPosOutOfRange(Vector2I arrayPos) { + return arrayPos.X < 0 || arrayPos.X >= Rows || arrayPos.Y < 0 || arrayPos.Y >= Cols; + } + + public virtual IPiece GetPiece(Vector2I arrayPos) { + if (IsPosOutOfRange(arrayPos)) return null; + return pieces[arrayPos.X, arrayPos.Y]; + } + + public virtual IPiece SetPiece(IPiece piece, Vector2I pos) { + if (IsPosOutOfRange(pos)) return null; + + IPiece oldPiece = pieces[pos.X, pos.Y]; + pieces[pos.X, pos.Y] = piece; + if (piece != null) piece.Pos = pos; + // if (oldPiece != null) oldPiece.Pos = Vector2I.Zero; + + OnSetPiece?.Invoke(this, new SetPieceEventArgs { OldPiece = oldPiece, NewPiece = piece, Pos = pos }); + return oldPiece; + } + + public virtual bool InsertPiece(IPiece piece, Vector2I pos) { + if (GetPiece(pos) != null && piece == null) { + return false; + } + + OnInsert?.Invoke(this, piece); + SetPiece(piece, pos); + return true; + } + + public virtual bool MovePiece(Vector2I from, Vector2I to) { + if (IsPosOutOfRange(to) || IsPosOutOfRange(from)) { + return false; + } + + IPiece piece = GetPiece(from); + if (GetPiece(to) != null && piece == null) { + return false; + } + + OnMove?.Invoke(this, new MoveEventArgs { From = from, To = to, Piece = piece }); + SetPiece(null, from); + SetPiece(piece, to); + return true; + } + + public virtual IPiece RemovePiece(Vector2I pos) { + IPiece piece = GetPiece(pos); + OnRemove?.Invoke(this, piece); + return SetPiece(null, pos); + } + + public virtual void Clear() { Clear(true); } + + public virtual void Clear(bool clearRecords) { + for (int i = 0; i < Rows; i++) { + for (int j = 0; j < Cols; j++) { + RemovePiece(new Vector2I(i, j)); + } + } + if (clearRecords) ClearRecords(); + } + + public virtual void ClearRecords() { + moveRecords.Clear(); + } + + public virtual void AddRecord(IPiece From, IPiece To, Vector2I FromPos, Vector2I ToPos) { + if (moveRecords.Count >= MAX_RECORDS) { + moveRecords.RemoveAt(0); + } + MoveRecord record = new(From, To, FromPos, ToPos); + + OnAddRecord?.Invoke(this, record); + moveRecords.Add(record); + } + + public virtual void UndoRecord() { + if (moveRecords.Count == 0) return; + MoveRecord record = moveRecords[^1]; + moveRecords.RemoveAt(moveRecords.Count - 1); + + // 恢复新位置的棋子,order is very inmportant + if (record.From != null) { + MovePiece(record.ToPos, record.FromPos); + } + + // 恢复旧位置的棋子 + if (record.To != null) { + InsertPiece(record.To, record.ToPos); + } + + OnUndoRecord?.Invoke(this, record); + } + + public class MoveRecord { + public IPiece From { get; } + public IPiece To { get; } + public Vector2I FromPos { get; } + public Vector2I ToPos { get; } + + public MoveRecord(IPiece From, IPiece To, Vector2I FromPos, Vector2I ToPos) { + this.From = From; + this.To = To; + this.ToPos = ToPos; + this.FromPos = FromPos; + } + } +} diff --git a/Scripts/Src/AbstractPiece.cs b/Scripts/Src/AbstractPiece.cs new file mode 100644 index 0000000..f5cdfb2 --- /dev/null +++ b/Scripts/Src/AbstractPiece.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Vector2I = Vector.Vector2I; + +public abstract class AbstractPiece : IPiece { + private Vector2I pos; // 注意这个坐标的非像素坐标而是棋盘坐标 + private bool isSelected = false; + protected string name; + private Dictionary data = new(); + + public Vector2I Pos { + get => pos; + set { + if (pos != value) { + var oldPos = pos; + pos = value; + OnPos?.Invoke(this, oldPos); + } + } + } + + public bool IsSelected { + get => isSelected; + set { + if (isSelected != value) { + var oldIsSelected = isSelected; + isSelected = value; + OnSelected?.Invoke(this, oldIsSelected); + } + } + } + + public string Name { + get => name; + set { + if (name != value) { + var oldName = name; + name = value; + OnName?.Invoke(this, oldName); + } + } + } + + public Dictionary Data { + get => data; + set => data = value; + } + + public event EventHandler OnPos; + public event EventHandler OnMove; + public event EventHandler OnSelected; + public event EventHandler OnName; + + public virtual bool Move(Vector2I pos) { + if (!CanMove(pos)) { + return false; + } + Pos = pos; + OnMove?.Invoke(this, pos); + return true; + } + + public abstract bool CanMove(Vector2I to); + + public override string ToString() { + return $"{Name} at {pos}"; + } + + public AbstractPiece(string name = "", Vector2I pos = null) { + this.name = name; + this.pos = pos ?? new(); + } +} diff --git a/Scripts/Src/ChineseChess/CCBoard.cs b/Scripts/Src/ChineseChess/CCBoard.cs new file mode 100644 index 0000000..999c33f --- /dev/null +++ b/Scripts/Src/ChineseChess/CCBoard.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections; +using static ChineseChess.ChessCore; +using Vector2I = Vector.Vector2I; + +namespace ChineseChess; +public class CCBoard : AbstractBoard +{ + public event EventHandler OnStepsChanged; + private int steps = 0; + public int Steps { + get => steps; + protected set { + if (steps != value) { + steps = value; + OnStepsChanged?.Invoke(this, steps); + } + } + } + public CCBoard() : base(9, 10) { + } + + public override void AddRecord(IPiece From, IPiece To, Vector2I FromPos, Vector2I ToPos) { + base.AddRecord(From, To, FromPos, ToPos); + Steps += 1; + } + + public override void UndoRecord() { + base.UndoRecord(); + if (Steps > 0) Steps -= 1; + } + + public (ArrayList, ArrayList) InitGame() { + Steps = 0; + // ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, new[] { + // new CCChariot(TurnsSideType.Black, "black chariot 0", 0, 0), + // ("车", 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) + // }); + ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, new CCPiece[] { + new CCChariot (this, TurnsSideType.Black, new Vector2I( 4, 0)), // 4,0 + new CCHorse (this, TurnsSideType.Black, new Vector2I( 3, 0)), // 3,0 + new CCElephant(this, TurnsSideType.Black, new Vector2I( 2, 0)), // 2,0 + new CCAdvisor (this, TurnsSideType.Black, new Vector2I( 1, 0)), // 1,0 + new CCGeneral (this, TurnsSideType.Black, new Vector2I( 0, 0)), // 0,0 + new CCAdvisor (this, TurnsSideType.Black, new Vector2I(-1, 0)), // -1,0 + new CCElephant(this, TurnsSideType.Black, new Vector2I(-2, 0)), // -2,0 + new CCHorse (this, TurnsSideType.Black, new Vector2I(-3, 0)), // -3,0 + new CCChariot (this, TurnsSideType.Black, new Vector2I(-4, 0)), // -4,0 + new CCCannon (this, TurnsSideType.Black, new Vector2I( 3, 2)), // 3,2 + new CCCannon (this, TurnsSideType.Black, new Vector2I(-3, 2)), // -3,2 + new CCPawn (this, TurnsSideType.Black, new Vector2I(-4, 3)), // -4,3 + new CCPawn (this, TurnsSideType.Black, new Vector2I(-2, 3)), // -2,3 + new CCPawn (this, TurnsSideType.Black, new Vector2I( 0, 3)), // 0,3 + new CCPawn (this, TurnsSideType.Black, new Vector2I( 2, 3)), // -4,3 + new CCPawn (this, TurnsSideType.Black, new Vector2I( 4, 3)) // 4,3 + }); + + ArrayList redPart = InitOnePartPieces(TurnsSideType.Red, new CCPiece[] { + new CCChariot (this, TurnsSideType.Red, new Vector2I( 4, 0)), // 4,0 + new CCHorse (this, TurnsSideType.Red, new Vector2I( 3, 0)), // 3,0 + new CCElephant(this, TurnsSideType.Red, new Vector2I( 2, 0)), // 2,0 + new CCAdvisor (this, TurnsSideType.Red, new Vector2I( 1, 0)), // 1,0 + new CCGeneral (this, TurnsSideType.Red, new Vector2I( 0, 0)), // 0,0 + new CCAdvisor (this, TurnsSideType.Red, new Vector2I(-1, 0)), // -1,0 + new CCElephant(this, TurnsSideType.Red, new Vector2I(-2, 0)), // -2,0 + new CCHorse (this, TurnsSideType.Red, new Vector2I(-3, 0)), // -3,0 + new CCChariot (this, TurnsSideType.Red, new Vector2I(-4, 0)), // -4,0 + new CCCannon (this, TurnsSideType.Red, new Vector2I( 3, 2)), // 3,2 + new CCCannon (this, TurnsSideType.Red, new Vector2I(-3, 2)), // -3,2 + new CCPawn (this, TurnsSideType.Red, new Vector2I(-4, 3)), // -4,3 + new CCPawn (this, TurnsSideType.Red, new Vector2I(-2, 3)), // -2,3 + new CCPawn (this, TurnsSideType.Red, new Vector2I( 0, 3)), // 0,3 + new CCPawn (this, TurnsSideType.Red, new Vector2I( 2, 3)), // 2,3 + new CCPawn (this, TurnsSideType.Red, new Vector2I( 4, 3)) // 4,3 + }); + + return (blackPart, redPart); + } + + private ArrayList InitOnePartPieces(TurnsSideType side, CCPiece[] pieces) { + ArrayList list = new(); + foreach (var piece in pieces) { + list.Add(piece); + InsertPiece(piece, piece.Pos); + } + return list; + } +} \ No newline at end of file diff --git a/Scripts/Src/ChineseChess/CCMain.cs b/Scripts/Src/ChineseChess/CCMain.cs new file mode 100644 index 0000000..9fa4ccd --- /dev/null +++ b/Scripts/Src/ChineseChess/CCMain.cs @@ -0,0 +1,107 @@ +using Vector2 = Vector.Vector2I; +using System; +using System.Collections; +namespace ChineseChess; + +public 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 CCBoard board = new(); + private readonly Player playerSelf; + private readonly Player playerOpponent; + // public EventHandler OnMove; + public ChessCore(Mode mode, TurnsSideType selfSide) { + this.selfSide = selfSide; + + playerSelf = new(board, Player.PlayerType.Human); + playerOpponent = new(board, Player.PlayerType.Human); + + void func(object _self, IBoard.MoveEventArgs record) { + // 防止 Undo 时 Selected Clear 出现 Null Pointer Exception + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + board.AddRecord(board.GetPiece(record.From), board.GetPiece(record.To), + record.From, record.To); + } + playerSelf.OnMove += func; + playerOpponent.OnMove += func; + switch (mode) { + case Mode.SingleMode: + break; + case Mode.MultiMode: + break; + default: + case Mode.DebugMode: + throw new NotImplementedException(); + } + } + + public void Init() { + (ArrayList blackPart, ArrayList redPart) = board.InitGame(); + if (selfSide == TurnsSideType.Red) { + playerSelf.SetAllowedPieces(redPart); + playerOpponent.SetAllowedPieces(blackPart); + } else { + playerSelf.SetAllowedPieces(blackPart); + playerOpponent.SetAllowedPieces(redPart); + } + } + + public void OnPosClicked(Vector2 pos, PlayerSideType clickedSide = PlayerSideType.Any) { + if (GetTurnsType() == 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; + } + } + + public TurnsSideType GetTurnsType() { + sideType = board.Steps % 2 == 0 ? TurnsSideType.Red : TurnsSideType.Black; + return sideType; + } + + public void Undo() { + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + board.UndoRecord(); + } + + public void ReInit() { + playerSelf.SelectedClear(); + playerOpponent.SelectedClear(); + board.Clear(true); + Init(); + } +} \ No newline at end of file diff --git a/Scripts/Src/ChineseChess/CCPiece.cs b/Scripts/Src/ChineseChess/CCPiece.cs new file mode 100644 index 0000000..d18e247 --- /dev/null +++ b/Scripts/Src/ChineseChess/CCPiece.cs @@ -0,0 +1,81 @@ +namespace ChineseChess; + +using System; +using System.Collections.Generic; +using System.Linq; +using Vector; +using static ChineseChess.ChessCore; + +public class CCPiece : AbstractPiece { + public string CNName { get; protected set; } + public TurnsSideType TurnsSide { get; } + protected CCBoard board; + protected Vector2I localPos = new(); + + static readonly Trans2DI transRedGlobal2Local = + new(new Vector2I(1, 0), new Vector2I(0, -1), new Vector2I(-4, 9)); + static readonly Trans2DI transBlackGlobal2Local = + new(new Vector2I(-1, 0), new Vector2I(0, 1), new Vector2I(4, 0)); + public CCPiece(CCBoard board = null, TurnsSideType turnsSide = TurnsSideType.Red, + string name = "", Vector2I pos = null) : base(name) { + this.board = board; + TurnsSide = turnsSide; + OnPos += (sender, args) => { + localPos = Global2Local(Pos); + }; + Pos = pos != null ? Local2Global(pos) : Vector2I.Zero; + } + + public override bool CanMove(Vector2I to) { + return CanMoveAllPos().Any(item => item == to); + } + + protected Vector2I Local2Global(in Vector2I pos) { + return TurnsSide == TurnsSideType.Red ? (pos * transRedGlobal2Local) : (pos * transBlackGlobal2Local); + } + + protected Vector2I Global2Local(in Vector2I pos) { + return TurnsSide == TurnsSideType.Red ? (transRedGlobal2Local * pos) : (transBlackGlobal2Local * pos); + } + + protected CCPiece GetCCPieceLocal(in Vector2I pos) { + return (CCPiece)board.GetPiece(Local2Global(pos)); + } + + public virtual List CanMoveAllPosSelf() { + return new List(); + } + + public IEnumerable CanMoveAllPosLocal() { + var self = CanMoveAllPosSelf().Select(item => item + localPos);// 转换局部坐标 + var ret = self.Where(item => { + bool ret = GetCCPieceLocal(item) == null || GetCCPieceLocal(item).TurnsSide != TurnsSide; + // Console.WriteLine($"{item} can move: {ret}"); + return ret; + }); // 过滤无效位置 + + // Console.WriteLine("localPos: {0}", localPos); + // Console.WriteLine("CanMoveAllPosSelf: {0}", string.Join(",", self)); + // Console.WriteLine("CanMoveAllPosLocal: {0}", string.Join(",", ret)); + return ret; + } + + public IEnumerable CanMoveAllPos() { + var ret = CanMoveAllPosLocal() + .Select(item => Local2Global(item)); // 转换为全局坐标 + // Console.WriteLine("CanMoveAllPos: {0}", string.Join(",", ret)); + return ret; + } + + protected bool IsPosOutOfRangeLocal(Vector2I pos) { + return board.IsPosOutOfRange(Local2Global(pos)); + } + + protected CCPiece GetRecursivePieceLocal(Vector2I origin, Vector2I pos) { + Vector2I with = origin + pos; + while (!IsPosOutOfRangeLocal(with) && GetCCPieceLocal(with) == null) { + with += pos; + } + return GetCCPieceLocal(with); + } +} diff --git a/Scripts/Src/ChineseChess/Player.cs b/Scripts/Src/ChineseChess/CCPlayer.cs similarity index 54% rename from Scripts/Src/ChineseChess/Player.cs rename to Scripts/Src/ChineseChess/CCPlayer.cs index d8ac1bf..b9b6d54 100644 --- a/Scripts/Src/ChineseChess/Player.cs +++ b/Scripts/Src/ChineseChess/CCPlayer.cs @@ -1,12 +1,13 @@ -using Vector2 = Godot.Vector2; using System; using System.Collections; +using Vector2I = Vector.Vector2I; +namespace ChineseChess; public class Player { - private readonly VirtualBoard board; + private readonly CCBoard board; private readonly SelectedPiece selectedNode; - public EventHandler OnMove; + public EventHandler OnMove; public bool CanMove { get; set; } = true; public enum PlayerType { @@ -14,16 +15,16 @@ public class Player { AI } - public Player(VirtualBoard board, PlayerType type = PlayerType.Human) + public Player(CCBoard board, PlayerType type = PlayerType.Human) { this.board = board; this.selectedNode = new SelectedPiece(board); } - public void HandleBoardPosClick(Vector2 clickPos) { - if (board.ArrPosOutOfRange(clickPos)) return; - // GD.Print($"VirtualBoard {clickPos} clicked"); - VirtualPiece clickChess = board.GetPiece(clickPos); + public void HandleBoardPosClick(Vector2I clickPos) { + if (board.IsPosOutOfRange(clickPos)) return; + // Console.WriteLine($"VirtualBoard {clickPos} clicked"); + IPiece clickChess = board.GetPiece(clickPos); if (!selectedNode.HasSelected()) { // Select piece @@ -42,22 +43,23 @@ public class Player { } } - public void MoveAndRecord(Vector2 toPos, Vector2 fromPos) { + public void MoveAndRecord(Vector2I toPos, Vector2I fromPos) { // GD.Print($"{fromPos} move to {toPos}"); - VirtualPiece toChess = board.GetPiece(toPos); - VirtualPiece fromChess = board.GetPiece(fromPos); - fromChess?.Selected(false); + IPiece toChess = board.GetPiece(toPos); + IPiece fromChess = board.GetPiece(fromPos); + if (fromChess != null) fromChess.IsSelected = false; - // MUST BE THERE !!! 防止删除节点后在启动回调导致错误 - OnMove?.Invoke(this, new VirtualBoard.MoveEventArgs { From = fromPos, To = toPos }); - - VirtualPiece NowNode; - if (toChess != null) { - NowNode = toChess; - board.RemovePiece(toPos); - } else { - NowNode = toChess; + selectedNode.Clear(); + if (!fromChess.CanMove(toPos)) { + return; } + + // MUST BE THERE !!! 防止删除节点后在启动回调导致错误 + OnMove?.Invoke(this, new IBoard.MoveEventArgs { From = fromPos, To = toPos }); + + if (toChess != null) { + board.RemovePiece(toPos); + } board.MovePiece(fromPos, toPos); selectedNode.Clear(); @@ -73,42 +75,43 @@ public class Player { private class SelectedPiece { // Called when the node enters the scene tree for the first time. - private Vector2 selectedNodePos = Vector2.Inf; - private VirtualPiece piece; - private readonly VirtualBoard board; + private Vector2I selectedNodePos = Vector2I.MaxValue; + private IPiece piece; + private readonly CCBoard board; public ArrayList allowedPieces = null; - public SelectedPiece(VirtualBoard board) { + public SelectedPiece(CCBoard board) { this.board = board; } public void Clear() { - if (selectedNodePos != Vector2.Inf) { - selectedNodePos = Vector2.Inf; - piece.Selected(false); + if (selectedNodePos != Vector2I.MaxValue) { + selectedNodePos = Vector2I.MaxValue; + piece.IsSelected = false; } + // Console.WriteLine("SelectedPiece.Clear {0}", piece); } - public void SetPos(Vector2 pos) { - // piece = board.GetNodeFromBoard(pos) as VirtualPiece; + public void SetPos(Vector2I pos) { piece = board.GetPiece(pos); if (allowedPieces != null && allowedPieces.Contains(piece) == false) { return; } selectedNodePos = pos; - piece.Selected(true); + piece.IsSelected = true; + Console.WriteLine("SelectedPiece.SetPos {0}", piece); } - public VirtualPiece GetPiece() { + public IPiece GetPiece() { return piece; } - public Vector2 GetPos() { + public Vector2I GetPos() { return selectedNodePos; } public bool HasSelected() { - return selectedNodePos != Vector2.Inf; + return selectedNodePos != Vector2I.MaxValue; } } } diff --git a/Scripts/Src/ChineseChess/CCTypes.cs b/Scripts/Src/ChineseChess/CCTypes.cs new file mode 100644 index 0000000..dfe913b --- /dev/null +++ b/Scripts/Src/ChineseChess/CCTypes.cs @@ -0,0 +1,246 @@ +using System.Collections.Generic; +using static ChineseChess.ChessCore; +using Vector2I = Vector.Vector2I; + +namespace ChineseChess; +public class CCGeneral : CCPiece { + public CCGeneral(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "General", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "帅"; + } else { + CNName = "将"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new() { + new(1, 0), + new(-1, 0), + new(0, 1), + new(0, -1), + }; + + // 移除不符合条件的元素 + list.RemoveAll(item => + localPos.X + item.X > 1 || localPos.X + item.X < -1 || + localPos.Y + item.Y > 2 || localPos.Y + item.Y < 0 + || GetRecursivePieceLocal(localPos + item, new(0, 1)) is CCGeneral); + return list; + } +} + +public class CCAdvisor : CCPiece { + public CCAdvisor(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Advisor", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "仕"; + } else { + CNName = "士"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new() { + new(1, 1), + new(-1, 1), + new(1, -1), + new(-1, -1), + }; + + // 移除不符合条件的元素 + list.RemoveAll(item => + localPos.X + item.X > 1 || localPos.X + item.X < -1 || + localPos.Y + item.Y > 2 || localPos.Y + item.Y < 0); + + return list; + } +} + +public class CCElephant : CCPiece { + public CCElephant(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Bishop", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "相"; + } else { + CNName = "象"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new() { + new(2, 2), + new(-2, 2), + new(2, -2), + new(-2, -2), + }; + + list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item)); + // 移除不符合条件的元素 + list.RemoveAll(item => localPos.Y + item.Y < 0 || localPos.Y + item.Y > 4 || + GetCCPieceLocal(new(localPos.X + item.X / 2, localPos.Y + item.Y / 2)) is not null); + + return list; + } +} + +public class CCHorse : CCPiece { + public CCHorse(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Horse", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "馬"; + } else { + CNName = "马"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new () { + new Vector2I(1, 2), + new Vector2I(1, -2), + new Vector2I(2, 1), + new Vector2I(2, -1), + new Vector2I(-1, -2), + new Vector2I(-1, 2), + new Vector2I(-2, -1), + new Vector2I(-2, 1), + }; + + list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item)); + list.RemoveAll(item => { + Vector2I pos = new(localPos); + if (item.X == 2) { + pos.X += 1; + } else if (item.X == -2) { + pos.X -= 1; + } else if (item.Y == 2) { + pos.Y += 1; + } else if (item.Y == -2) { + pos.Y -= 1; + } + return GetCCPieceLocal(pos) is not null; + }); + return list; + } +} + +public class CCChariot : CCPiece { + public CCChariot(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Chariot", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "車"; + } else { + CNName = "车"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new (); + + void func(Vector2I added) { + Vector2I ptr = new(localPos); + while (true) { + ptr += added; + if (IsPosOutOfRangeLocal(ptr)) { + break; + } + if (GetCCPieceLocal(ptr) != null) { + list.Add(ptr); + break; + } + list.Add(ptr - localPos); + } + } + + func(Vector2I.Up); + func(Vector2I.Down); + func(Vector2I.Left); + func(Vector2I.Right); + return list; + } +} + +public class CCCannon : CCPiece { + public CCCannon(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Cannon", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "砲"; + } else { + CNName = "炮"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new (); + + void func(Vector2I added) { + Vector2I ptr = new(localPos); + bool flag = true; + while (true) { + ptr += added; + if (IsPosOutOfRangeLocal(ptr)) { + break; + } + if (GetCCPieceLocal(ptr) != null) { + if (flag) { + flag = false; + } else { + list.Add(ptr - localPos); + break; + } + } + if (flag) list.Add(ptr - localPos); + } + } + + func(Vector2I.Up); + func(Vector2I.Down); + func(Vector2I.Left); + func(Vector2I.Right); + return list; + } +} + +public class CCPawn : CCPiece { + public CCPawn(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null) + : base(board, turnsSide, name : "Pawn", pos) { + if (turnsSide == TurnsSideType.Red) { + CNName = "兵"; + } else { + CNName = "卒"; + } + } + + public override List CanMoveAllPosSelf() { + List list = new () { + new(0, 1), + new(1, 0), + new(-1, 0), + }; + + list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item)); + list.RemoveAll(item => localPos.Y <= 4 && item != new Vector2I(0, 1)); + return list; + } +} + +// 帅/将 (General) - 代表双方的最高统帅。 +// 子类名:ChessGeneral + +// 仕/士 (Advisor) - 保护帅/将的近身侍卫。 +// 子类名:ChessAdvisor + +// 相/象 (Elephant) - 行动受限,走田字格,不能过河。 +// 子类名:ChessElephant + +// 車/车 (Chariot) - 横竖移动,威力巨大。 +// 子类名:ChessChariot + +// 馬/马 (Horse) - 走日字形,跳跃式移动。 +// 子类名:ChessHorse + +// 砲/炮 (Cannon) - 需要隔子才能吃子,直线移动。 +// 子类名:ChessCannon + +// 兵/卒 (Pawn) - 最基础的棋子,过河后可横移。 +// 子类名:ChessPawn \ No newline at end of file diff --git a/Scripts/Src/ChineseChess/ChessAdvisor.cs b/Scripts/Src/ChineseChess/ChessAdvisor.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessCannon.cs b/Scripts/Src/ChineseChess/ChessCannon.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessChariot.cs b/Scripts/Src/ChineseChess/ChessChariot.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessElephant.cs b/Scripts/Src/ChineseChess/ChessElephant.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessGeneral.cs b/Scripts/Src/ChineseChess/ChessGeneral.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessHorse.cs b/Scripts/Src/ChineseChess/ChessHorse.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessPawn.cs b/Scripts/Src/ChineseChess/ChessPawn.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Scripts/Src/ChineseChess/ChessTypes.cs b/Scripts/Src/ChineseChess/ChessTypes.cs deleted file mode 100644 index 3fe7455..0000000 --- a/Scripts/Src/ChineseChess/ChessTypes.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 帅/将 (General) - 代表双方的最高统帅。 -// 子类名:ChessGeneral - -// 仕/士 (Advisor) - 保护帅/将的近身侍卫。 -// 子类名:ChessAdvisor - -// 相/象 (Elephant) - 行动受限,走田字格,不能过河。 -// 子类名:ChessElephant - -// 車/车 (Chariot) - 横竖移动,威力巨大。 -// 子类名:ChessChariot - -// 馬/马 (Horse) - 走日字形,跳跃式移动。 -// 子类名:ChessHorse - -// 砲/炮 (Cannon) - 需要隔子才能吃子,直线移动。 -// 子类名:ChessCannon - -// 兵/卒 (Pawn) - 最基础的棋子,过河后可横移。 -// 子类名:ChessPawn \ No newline at end of file diff --git a/Scripts/Src/ChineseChess/ChineseChess.cs b/Scripts/Src/ChineseChess/ChineseChess.cs deleted file mode 100644 index 37697a7..0000000 --- a/Scripts/Src/ChineseChess/ChineseChess.cs +++ /dev/null @@ -1,154 +0,0 @@ -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/IBoard.cs b/Scripts/Src/IBoard.cs new file mode 100644 index 0000000..5a83cf2 --- /dev/null +++ b/Scripts/Src/IBoard.cs @@ -0,0 +1,32 @@ +using System; +using Vector2I = Vector.Vector2I; + +public interface IBoard { + public int Rows { get; } + public int Cols { get; } + + public class SetPieceEventArgs : EventArgs { + public IPiece OldPiece { get; set; } + public IPiece NewPiece { get; set; } + public Vector2I Pos { get; set; } + } + + public class MoveEventArgs : EventArgs { + public IPiece Piece { get; set; } + public Vector2I From { get; set; } + public Vector2I To { get; set; } + } + + event EventHandler OnInsert; + event EventHandler OnRemove; + event EventHandler OnMove; + event EventHandler OnSetPiece; + + bool IsPosOutOfRange(Vector2I pos); + IPiece GetPiece(Vector2I pos); + IPiece SetPiece(IPiece piece, Vector2I pos); + bool InsertPiece(IPiece piece, Vector2I pos); + bool MovePiece(Vector2I from, Vector2I to); + IPiece RemovePiece(Vector2I pos); + void Clear(); +} diff --git a/Scripts/Src/IPiece.cs b/Scripts/Src/IPiece.cs new file mode 100644 index 0000000..9d4725e --- /dev/null +++ b/Scripts/Src/IPiece.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Vector2I = Vector.Vector2I; + +public interface IPiece { + Vector2I Pos { get; set; } + string Name { get; set; } + bool IsSelected { get; set; } + Dictionary Data { get; set; } + + event EventHandler OnPos; + event EventHandler OnMove; + event EventHandler OnSelected; + event EventHandler OnName; + + bool CanMove(Vector2I to); + bool Move(Vector2I pos); +} diff --git a/Scripts/Src/Vector.cs b/Scripts/Src/Vector.cs new file mode 100644 index 0000000..7f8a510 --- /dev/null +++ b/Scripts/Src/Vector.cs @@ -0,0 +1,133 @@ +using System.Diagnostics; + +namespace Vector; + +public class Vector2I { + public Vector2I() { + X = 0; + Y = 0; + } + + public Vector2I(int x, int y) { + X = x; + Y = y; + } + + public Vector2I(Vector2I other) { + X = other.X; + Y = other.Y; + } + + public int X { get; set; } + public int Y { get; set; } + + public static bool operator ==(Vector2I a, Vector2I b) { + return a?.X == b?.X && a?.Y == b?.Y; + } + + public static bool operator !=(Vector2I a, Vector2I b) { + return !(a == b); + } + + public static Vector2I operator +(Vector2I left, Vector2I right) { + return new ( + left.X + right.X, + left.Y + right.Y); + } + + public static Vector2I operator -(Vector2I left, Vector2I right) { + return new ( + left.X - right.X, + left.Y - right.Y); + } + + public static Vector2I operator *(Vector2I left, Vector2I right) { + return new ( + left.X * right.X, + left.Y * right.Y); + } + + public int Dot(Vector2I with) { + return X * with.X + Y * with.Y; + } + + public override bool Equals(object obj) { + if (obj is Vector2I other) { + return this == other; + } + return false; + } + + public override int GetHashCode() { + return System.HashCode.Combine(X, Y); + } + + public static readonly Vector2I MaxValue = new(int.MaxValue, int.MinValue); + public static readonly Vector2I MinValue = new (int.MinValue, int.MaxValue); + public static readonly Vector2I Zero = new(0, 0); + + public static readonly Vector2I Up = new(0, 1); + public static readonly Vector2I Down = new(0, -1); + public static readonly Vector2I Left = new(-1, 0); + public static readonly Vector2I Right = new(1, 0); + + public override string ToString() { + return $"({X},{Y})"; + } +} + +public class Trans2DI { + public Vector2I XAxis { get; set; } // x axis , first column + public Vector2I YAxis { get; set; } // y axis , second column + public Vector2I Origin { get; set; } // origin point + public Trans2DI() { + XAxis = new Vector2I(1, 0); + YAxis = new Vector2I(0, 1); + Origin = new Vector2I(0, 0); + } + + public Trans2DI(Vector2I xAxis, Vector2I yAxis, Vector2I origin) { + XAxis = xAxis; + YAxis = yAxis; + Origin = origin; + } + + public Trans2DI(Trans2DI other) { + XAxis = other.XAxis; + YAxis = other.YAxis; + Origin = other.Origin; + } + + public Trans2DI(int xx, int xy, int yx, int yy, int ox, int oy) { + XAxis = new Vector2I(xx, xy); + YAxis = new Vector2I(yx, yy); + Origin = new Vector2I(ox, oy); + } + + public static Trans2DI Identity => new(); + + /** + * X.x Y.x * v.x + O.x = X.x * v.x + Y.x * v.y + O.x + * X.y Y.y v.y O.y X.y * v.x + Y.y * v.y + O.y + */ + public static Vector2I operator *(in Trans2DI trans, in Vector2I vec) { + return new Vector2I( + trans.XAxis.X * vec.X + trans.XAxis.Y * vec.Y, + trans.YAxis.X * vec.X + trans.YAxis.Y * vec.Y) + trans.Origin; + } + + public static Vector2I operator *(in Vector2I vec, in Trans2DI trans) { + Vector2I with = vec - trans.Origin; + return new Vector2I( + trans.XAxis.X * with.X + trans.XAxis.Y * with.Y, + trans.YAxis.X * with.X + trans.YAxis.Y * with.Y); + } + + public static Trans2DI operator *(Trans2DI a, Trans2DI b) { + return new Trans2DI(); + } + + public override string ToString() { + return $"[{XAxis}, {YAxis}, {Origin}]"; + } +} diff --git a/Scripts/Src/VirtualBoard.cs b/Scripts/Src/VirtualBoard.cs deleted file mode 100644 index 08044c3..0000000 --- a/Scripts/Src/VirtualBoard.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using Vector2 = Godot.Vector2; - -public class VirtualBoard { - private readonly int Rows; - private readonly int Cols; - private readonly VirtualPiece[,] pieces; - - public class SetPiecePosEventArgs : EventArgs - { - public VirtualPiece OldPiece { get; set; } - public VirtualPiece NewPiece { get; set; } - } - - public class MoveEventArgs : EventArgs - { - public Vector2 From { get; set; } - public Vector2 To { get; set; } - } - - public event EventHandler OnSetPiecePos; - public event EventHandler OnInsert; - public event EventHandler OnRemove; - public event EventHandler OnMove; - - public VirtualBoard(int rows, int cols) { - this.Rows = rows; - this.Cols = cols; - pieces = new VirtualPiece[rows, cols]; - } - - public bool ArrPosOutOfRange(Vector2 arrayPos) { - return arrayPos.X < 0 || arrayPos.X >= Rows || arrayPos.Y < 0 || arrayPos.Y >= Cols; - } - - public VirtualPiece GetPiece(Vector2 arrayPos) { - if (ArrPosOutOfRange(arrayPos)) return null; - return pieces[(int)arrayPos.X, (int)arrayPos.Y]; - } - - public VirtualPiece SetPiecePos(VirtualPiece piece, Vector2 arrayPos) { - if (ArrPosOutOfRange(arrayPos)) return null; - - VirtualPiece oldPiece = pieces[(int)arrayPos.X, (int)arrayPos.Y]; - pieces[(int)arrayPos.X, (int)arrayPos.Y] = piece; - - OnSetPiecePos?.Invoke(this, new SetPiecePosEventArgs { OldPiece = oldPiece, NewPiece = piece }); - - piece?.Move(arrayPos); - - return oldPiece; - } - - public bool InsertPiece(VirtualPiece piece, Vector2 arrayPos) { - if (GetPiece(arrayPos) != null) { - return false; - } - OnInsert?.Invoke(this, piece); - - SetPiecePos(piece, arrayPos); - return true; - } - - public bool MovePiece(Vector2 from, Vector2 to) { - if (ArrPosOutOfRange(to) || ArrPosOutOfRange(from)) { - return false; - } - - if (GetPiece(to) != null) { - return false; - } - - OnMove?.Invoke(this, new MoveEventArgs { From = from, To = to }); - - SetPiecePos(SetPiecePos(null, from), to); - return true; - } - - public bool RemovePiece(Vector2 pos) { - VirtualPiece piece = GetPiece(pos); - if (piece == null) { - return false; - } - - OnRemove?.Invoke(this, piece); - - return SetPiecePos(null, pos) != null; - } - - public void Clear() { - for (int i = 0; i < Rows; i++) { - for (int j = 0; j < Cols; j++) { - RemovePiece(new Vector2(i, j)); - } - } - } -} \ No newline at end of file diff --git a/Scripts/Src/VirtualPiece.cs b/Scripts/Src/VirtualPiece.cs deleted file mode 100644 index 163ac6d..0000000 --- a/Scripts/Src/VirtualPiece.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Vector2 = Godot.Vector2; -using System; - -public class VirtualPiece { - private Vector2 pos; // 注意这个坐标的非像素坐标而是棋盘坐标 - - public readonly string name; - private bool isSelected; - public object data; - - public event Action OnMove; - public event Action OnSelected; - - public void Move(Vector2 pos) { - this.pos = pos; - OnMove?.Invoke(pos); - } - - public Vector2 Pos() { - return pos; - } - - public void Selected(bool isSelected) { - if (this.isSelected != isSelected) { - OnSelected?.Invoke(isSelected); - this.isSelected = isSelected; - } - } - - public bool IsSelected() { - return isSelected; - } - - public VirtualPiece(string name = "", Vector2 pos = new Vector2()) { - this.name = name; - this.pos = pos; - } -} \ No newline at end of file diff --git a/Scripts/Utilities/MoveRecords.cs b/Scripts/Utilities/MoveRecords.cs deleted file mode 100644 index 602b330..0000000 --- a/Scripts/Utilities/MoveRecords.cs +++ /dev/null @@ -1,63 +0,0 @@ -// using System.Numerics; -using Godot; - -using System.Collections.Generic; -using System; - -public class MoveRecords { - private readonly LinkedList records = new LinkedList(); // 使用队列替换栈 - private readonly int maxRecords; // 记录上限 - private readonly Action onAddRecordCallback; // 添加记录时的回调 - private readonly Action onUndoRecordCallback; // 撤销记录时的回调 - - public MoveRecords( - Action onAddRecordCallback = null, - Action onUndoRecordCallback = null, - int maxRecords = 32) { - this.maxRecords = maxRecords; - this.onAddRecordCallback = onAddRecordCallback; - this.onUndoRecordCallback = onUndoRecordCallback; - } - - public void AddRecord(T newNode, T oldNode, Vector2 newPos, Vector2 oldPos) { - // 达到记录上限时,移除最远的记录(队首元素) - if (records.Count >= maxRecords) { - records.RemoveFirst(); - } - var record = new MoveRecord(newNode, oldNode, newPos, oldPos); - // 触发添加记录的回调 - onAddRecordCallback?.Invoke(newNode, oldNode, newPos, oldPos); - // GD.Print("In func Addrecord: ", record.NewNode, "->", record.OldNode, ":", record.NewPos, "->", record.OldPos); - records.AddLast(record); // 将新记录加入队尾 - } - - public int Count() { - return records.Count; - } - - public void Undo() { - if (records.Count == 0) return; - MoveRecord record = records.Last.Value; // 移除并获取队首的记录以执行撤销操作 - records.RemoveLast(); - // 触发撤销记录的回调 - onUndoRecordCallback?.Invoke(record.NewNode, record.OldNode, record.NewPos, record.OldPos); - } - - public void Clear() { - records.Clear(); - } - - private class MoveRecord { - public T NewNode { get; } - public T OldNode { get; } - public Vector2 NewPos { get; } - public Vector2 OldPos { get; } - - public MoveRecord(T newNode, T oldNode, Vector2 newPos, Vector2 oldPos) { - NewNode = newNode; - OldNode = oldNode; - OldPos = oldPos; - NewPos = newPos; - } - } -} \ No newline at end of file diff --git a/Scripts/Utilities/Transforms.cs b/Scripts/Utilities/Transforms.cs deleted file mode 100644 index 01aa646..0000000 --- a/Scripts/Utilities/Transforms.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Godot; - -public static class PosTrans { - private static readonly int pixGripSize = 32; - public static Transform2D transArrToPix = new( - new Vector2(pixGripSize, 0), - new Vector2(0, pixGripSize), - new Vector2(-4, -4.5f) * pixGripSize - ); -} \ No newline at end of file