Compare commits

...

2 Commits

Author SHA1 Message Date
ZZY
327c2df94d refactor(chess): 重构象棋程序基础结构,使用Nullable重构核心代码
- 更新了多个文件的代码结构和类型定义,提高了代码的健壮性和可维护性
- 优化了事件处理、棋子管理和移动逻辑,为后续功能扩展打下坚实基础
- 修复了一些潜在的空指针异常问题,提高了程序的稳定性
- 修复部分bug
2024-11-22 23:51:32 +08:00
ZZY
9c619784af feat(重构): 重构棋盘和棋子的逻辑
- 重构了 ChessBoard 和 ChessPiece 类的逻辑
- 添加了新版本的 ChineseChess 库
- 删除了旧版本的 VirtualBoard 和 VirtualPiece 类
- 更新了相关的场景和脚本
2024-11-22 20:01:40 +08:00
37 changed files with 1312 additions and 509 deletions

7
.gitignore vendored
View File

@ -25,3 +25,10 @@ bin/
*.sln *.sln
*.error *.error
*.key* *.key*
# cs project files
!*.csproj
# Test files
Test/bin
Test/obj

15
Chinese_Chess.csproj Normal file
View File

@ -0,0 +1,15 @@
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors> -->
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test\**\*" />
</ItemGroup>
</Project>

View File

@ -12,7 +12,11 @@ visible = false
script = ExtResource("3_26g24") script = ExtResource("3_26g24")
[node name="Layer0" type="TileMapLayer" parent="."] [node name="Layer0" type="TileMapLayer" parent="."]
use_parent_material = true show_behind_parent = true
position = Vector2(-8, 184) 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_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") tile_set = ExtResource("1_ws3cq")
collision_enabled = false
collision_visibility_mode = 2
navigation_enabled = false
navigation_visibility_mode = 2

View File

@ -152,6 +152,13 @@ size_flags_horizontal = 3
disabled = true disabled = true
text = "GetUserDataDir" 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/MarginContainer/Back" to="." method="OnBack"]
[connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"] [connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"]
[connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit" to="." method="OnServerUrlChanged"] [connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit" to="." method="OnServerUrlChanged"]

View File

@ -45,15 +45,18 @@ public partial class ChessGame : Node2D {
Game = new(ChessCore.Mode.SingleMode, sideSelf); Game = new(ChessCore.Mode.SingleMode, sideSelf);
} }
board.LoadBoard(Game.board); 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"; turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black";
}; };
board.OnPosClicked += (sender, pos) => { board.OnPosClicked += (sender, pos) => {
int posX = (int)pos.X;
int posY = (int)pos.Y;
Vector.Vector2I vector = new(posX, posY);
if (isSession) { if (isSession) {
Game.OnPosClicked(pos, ChessCore.PlayerSideType.Self); Game.OnPosClicked(vector, ChessCore.PlayerSideType.Self);
var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary { var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary {
{"type", "mouseClicked"}, {"type", "mouseClicked"},
{"X", pos.X}, {"X", pos.X},
@ -61,7 +64,7 @@ public partial class ChessGame : Node2D {
{"id", global.RPClient.GetUserId()} {"id", global.RPClient.GetUserId()}
}); });
} else { } 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(), Vector2 mouseClicked = new(GD.StrToVar(msg["X"].ToString()).AsInt32(),
GD.StrToVar(msg["Y"].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; break;
case "undo": case "undo":
Game.Undo(); Game.Undo();
@ -137,6 +141,7 @@ public partial class ChessGame : Node2D {
{"id", global.RPClient.GetUserId()}, {"id", global.RPClient.GetUserId()},
}); });
} else { } else {
if (Game.board.Steps == 0) board.Clear();
Game.Undo(); Game.Undo();
} }
} }
@ -150,6 +155,7 @@ public partial class ChessGame : Node2D {
}); });
} else { } else {
Game.ReInit(); Game.ReInit();
board.Clear();
} }
} }
} }

View File

@ -1,35 +1,57 @@
// Chessboard.cs // Chessboard.cs
using System; using System;
using Godot; using Godot;
using ChineseChess;
using Godot.Collections;
public partial class ChessBoard : Node2D { public partial class ChessBoard : Node2D {
public event EventHandler<Vector2> OnMouseClicked; public event EventHandler<Vector2> OnMouseClicked;
public event EventHandler<Vector2> OnPosClicked; public event EventHandler<Vector2> OnPosClicked;
public Vector2 from = Vector2.Inf, to = Vector2.Inf;
public Array<Vector2> canMovePos = new();
public override void _Ready() { 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) => { board.OnRemove += (sender, piece) => {
if (piece.data != null) { if (piece?.Data.TryGetValue("Godot", out object node) ?? false) {
RemoveChild(piece.data as Node); RemoveChild(node as Node);
} }
}; };
board.OnInsert += (sender, piece) => { board.OnInsert += (sender, piece) => {
if (piece.data != null && piece.data is Node) { ChessPiece chessPiece = null;
AddChild(piece.data as Node); if (piece.Data.TryGetValue("Godot", out object node)) {
chessPiece = node as ChessPiece;
} else { } else {
ChessPiece chessPiece = new(piece.name, piece); chessPiece = new((CCPiece)piece);
if (piece.Pos().Y >= 5) {
chessPiece.LabelColor = new Color("red");
} else {
chessPiece.LabelColor = new Color("black");
} }
chessPiece.ShowBehindParent = true;
AddChild(chessPiece); 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 _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) { public override void _Input(InputEvent @event) {
@ -41,4 +63,13 @@ public partial class ChessBoard : Node2D {
GetLocalMousePosition()).Round()); 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
);
}
} }

View File

@ -1,34 +1,46 @@
// Chesspiece.cs // Chesspiece.cs
using Godot; using Godot;
using ChineseChess;
using System.Linq;
using System;
public partial class ChessPiece : Sprite2D { public partial class ChessPiece : Sprite2D {
// 文字内容
[Export] [Export]
public string PieceLabel { get; set; } = null; public string PieceLabel { get; set; } = null;
// 文字颜色(可导出以编辑器调整) // Text Color (Can Export for Editor Adjust)
[Export] [Export]
public Color LabelColor { get; set; } = new Color("white"); public Color LabelColor { get; set; } = new Color("white");
private Vector2 textureSize; private Vector2 textureSize;
private Label labelOfChessName; private Label labelOfChessName;
private readonly VirtualPiece piece; private readonly IPiece piece;
public VirtualPiece GetVirtualPiece() { public IPiece GetVirtualPiece() {
return piece; return piece;
} }
private void OnMove(Vector2 newPos) { private void OnPos(object _self, Vector.Vector2I oldPos) {
Position = PosTrans.transArrToPix * new Vector2(newPos.X, newPos.Y); CCPiece self = (CCPiece)_self;
Position = ChessBoard.PosTrans.transArrToPix * new Vector2(self.Pos.X, self.Pos.Y);
} }
public void OnSelected(bool isSelected) { public void OnSelected(object _self, bool oldIsSelected) {
if (isSelected) { CCPiece self = (CCPiece)_self;
GD.Print($"{piece.Pos()} is selected"); ChessBoard chessBoard = GetParent() as ChessBoard;
if (self.IsSelected) {
GD.Print($"{piece.Pos} is selected");
Transform *= transToSeleted; Transform *= transToSeleted;
} else { foreach (var item in self.CanMoveAllPos()) {
GD.Print($"{piece.Pos()} is deselected"); chessBoard.canMovePos.Add(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y));
Transform *= transToSeleted.AffineInverse();
} }
} else {
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( Transform2D transToSeleted = new(
@ -37,15 +49,18 @@ public partial class ChessPiece : Sprite2D {
new Vector2(0, 0) new Vector2(0, 0)
); );
public ChessPiece() : this("", new()){ public ChessPiece() : this(new CCPiece()){
} }
public ChessPiece(string name, VirtualPiece piece) { public ChessPiece(CCPiece piece) {
PieceLabel = name; PieceLabel = piece.CNName;
this.piece = piece; 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.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. // Called when the node enters the scene tree for the first time.
@ -54,7 +69,6 @@ public partial class ChessPiece : Sprite2D {
} }
private void InitLabel() { private void InitLabel() {
// this.Texture.ResourcePath = "res://Asserts/ChesspieceBase.tres";
Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres"); Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres");
textureSize = Texture.GetSize(); textureSize = Texture.GetSize();
Vector2 labalPosition = new( Vector2 labalPosition = new(

View File

@ -4,21 +4,26 @@ using Godot.Collections;
namespace RPPackage { namespace RPPackage {
public partial class RPClientBaseEDWS : EventDrivenWebSocket { public partial class RPClientBaseEDWS : EventDrivenWebSocket {
public delegate void RPClientEventHandler(string cmd, Dictionary data); public delegate void RPClientEventHandler(string cmd, Dictionary? data);
public delegate void RPClientErrorHandler(string errCode, string type, string cmd, string errMsg); public delegate void RPClientErrorHandler(string errCode, string type, string cmd, string errMsg);
public event RPClientEventHandler OnRPCUser; public event RPClientEventHandler? OnRPCUser;
public event RPClientEventHandler OnRPCRegion; public event RPClientEventHandler? OnRPCRegion;
public event RPClientEventHandler OnRPCSession; public event RPClientEventHandler? OnRPCSession;
public event RPClientEventHandler OnRPCMsg; public event RPClientEventHandler? OnRPCMsg;
public event RPClientErrorHandler OnRPCError; public event RPClientErrorHandler? OnRPCError;
public RPClientBaseEDWS() : base() { public RPClientBaseEDWS() : base() {
OnText += (text) => { OnText += (text) => {
// GD.Print($"response: {text}"); // GD.Print($"response: {text}");
RPMessage msg = RPHelper.HandleIncomingMessage(text); RPMessage? msg = RPHelper.HandleIncomingMessage(text);
if (msg == null) {
MakeRPCError("9999", "Unknown Msg", "Error", null);
return;
}
if (msg.Code != "0000") { if (msg.Code != "0000") {
OnRPCError?.Invoke(msg.Code, msg.Type, msg.Cmd, msg.Data.ToString()); OnRPCError?.Invoke(msg.Code ?? "9999", msg.Type, msg.Cmd,
msg.Data?.ToString() ?? "");
return; return;
} }
switch (msg.Type) { switch (msg.Type) {
@ -35,32 +40,36 @@ public partial class RPClientBaseEDWS : EventDrivenWebSocket {
OnRPCMsg?.Invoke(msg.Cmd, msg.Data); OnRPCMsg?.Invoke(msg.Cmd, msg.Data);
break; break;
default: default:
OnRPCError?.Invoke(msg.Code, "unknown", msg.Cmd, msg.Data.ToString()); MakeRPCError(msg.Code, "unknown", msg.Cmd, msg.Data?.ToString());
break; break;
} }
}; };
} }
protected void MakeRPCError(string errCode, string type, string cmd, string errMsg) { protected void MakeRPCError(string errCode, string type, string cmd, string? errMsg) {
this.OnRPCError?.Invoke(errCode, type, cmd, errMsg ?? "null"); OnRPCError?.Invoke(errCode, type, cmd, errMsg ?? "null");
} }
} }
public partial class RPClientEDWS : RPClientBaseEDWS { public partial class RPClientEDWS : RPClientBaseEDWS {
string userName; string? userName;
string userId; string? userId;
string userToken; string? userToken;
string regionId; string? regionId;
public string GetUserId() { return userId; } public string? GetUserId() { return userId; }
public delegate void SessionRecvHandle(Dictionary msg); public delegate void SessionRecvHandle(Dictionary? msg);
public event SessionRecvHandle OnPRCSessionRecv; public event SessionRecvHandle? OnPRCSessionRecv;
public event RPClientEventHandler OnPRCSessionExit; public event RPClientEventHandler? OnPRCSessionExit;
public RPClientEDWS() : base() { public RPClientEDWS() : base() {
OnRPCUser += (cmd, msg) => { OnRPCUser += (cmd, msg) => {
if (msg == null) {
MakeRPCError("0000", "User", "unknown", "unknown");
return;
}
switch (cmd) { switch (cmd) {
case "init": case "init":
userId = msg["userId"].AsString(); userId = msg["userId"].AsString();
@ -113,7 +122,7 @@ public partial class RPClientEDWS : RPClientBaseEDWS {
OnRPCMsg += (cmd, msg) => { OnRPCMsg += (cmd, msg) => {
switch(cmd) { switch(cmd) {
case "echo": case "echo":
GD.Print(msg["_"].AsString()); GD.Print(msg?["_"].AsString());
break; break;
default: default:
break; break;
@ -148,7 +157,7 @@ public partial class RPClientEDWS : RPClientBaseEDWS {
} }
public delegate bool UserInitCallback(); public delegate bool UserInitCallback();
private UserInitCallback _userInitCallback; private UserInitCallback? _userInitCallback;
public bool UserInit(string userName, string fingerPrint, UserInitCallback callback) { public bool UserInit(string userName, string fingerPrint, UserInitCallback callback) {
if (this.GetIsConnected() == false) { if (this.GetIsConnected() == false) {
return false; return false;
@ -201,14 +210,14 @@ public partial class RPClientEDWS : RPClientBaseEDWS {
public delegate bool RegionInspectCallback( public delegate bool RegionInspectCallback(
Array<Dictionary<string, string>> _ Array<Dictionary<string, string>> _
); );
private RPClientEventHandler _regionRecvCallback; private RPClientEventHandler? _regionRecvCallback;
public bool RegionInspect(string regionId, RegionInspectCallback callback) { public bool RegionInspect(string regionId, RegionInspectCallback callback) {
if (this.GetIsConnected() == false || this.userId == null) { if (this.GetIsConnected() == false || this.userId == null) {
return false; return false;
} }
_regionRecvCallback = null; _regionRecvCallback = null;
_regionRecvCallback += (cmd, msg) => { _regionRecvCallback += (cmd, msg) => {
if (cmd != "inspect") { if (cmd != "inspect" || msg == null) {
return; return;
} }
callback(msg["_"].AsGodotArray<Dictionary<string, string>>()); callback(msg["_"].AsGodotArray<Dictionary<string, string>>());
@ -228,17 +237,18 @@ public partial class RPClientEDWS : RPClientBaseEDWS {
public delegate bool SessionAckCreateCallback( public delegate bool SessionAckCreateCallback(
string sessionId, string sessionId,
bool res, bool res,
string reqUserId, string? reqUserId,
string reqUserName string? reqUserName
); );
private RPClientEventHandler _sessionAckCreateCallback; private RPClientEventHandler? _sessionAckCreateCallback;
public void RegSessionAckCreateCallback(SessionAckCreateCallback recvCallback) { public void RegSessionAckCreateCallback(SessionAckCreateCallback recvCallback) {
_sessionAckCreateCallback = null; _sessionAckCreateCallback = null;
_sessionAckCreateCallback += (cmd, msg) => { _sessionAckCreateCallback += (cmd, msg) => {
if (msg == null) return;
string sessionId = msg["sessionId"].AsString(); string sessionId = msg["sessionId"].AsString();
bool res = msg.ContainsKey("res") && msg["res"].AsBool(); bool res = msg.ContainsKey("res") && msg["res"].AsBool();
string reqUserId = msg.ContainsKey("reqUserId") ? msg["reqUserId"].AsString() : null; string? reqUserId = msg.ContainsKey("reqUserId") ? msg["reqUserId"].AsString() : null;
string reqUserName = msg.ContainsKey("reqUserName") ? msg["reqUserName"].AsString() : null; string? reqUserName = msg.ContainsKey("reqUserName") ? msg["reqUserName"].AsString() : null;
recvCallback(sessionId, res, reqUserId, reqUserName); recvCallback(sessionId, res, reqUserId, reqUserName);
}; };
} }

View File

@ -9,12 +9,14 @@ public static class RPHelper
return message.ToDictionary(); return message.ToDictionary();
} }
public static RPMessage DeserializeRPMessage(Dictionary data) public static RPMessage? DeserializeRPMessage(Dictionary data)
{ {
return new RPMessage if (!data.ContainsKey("type") || !data.ContainsKey("cmd")) {
{ return null;
Type = data.ContainsKey("type") ? (string)data["type"] : null, }
Cmd = data.ContainsKey("cmd") ? (string)data["cmd"] : null, return new RPMessage {
Type = (string)data["type"],
Cmd = (string)data["cmd"],
Code = data.ContainsKey("code") ? (string)data["code"] : null, Code = data.ContainsKey("code") ? (string)data["code"] : null,
Uid = data.ContainsKey("uid") ? (string)data["uid"] : null, Uid = data.ContainsKey("uid") ? (string)data["uid"] : null,
Token = data.ContainsKey("token") ? (string)data["token"] : null, Token = data.ContainsKey("token") ? (string)data["token"] : null,
@ -28,7 +30,7 @@ public static class RPHelper
ws.SendJsonEx(RPHelper.SerializeRPMessage(message)); ws.SendJsonEx(RPHelper.SerializeRPMessage(message));
} }
public static RPMessage HandleIncomingMessage(string jsonMessage) public static RPMessage? HandleIncomingMessage(string jsonMessage)
{ {
var dataDict = Json.ParseString(jsonMessage).AsGodotDictionary(); var dataDict = Json.ParseString(jsonMessage).AsGodotDictionary();
if (dataDict != null) if (dataDict != null)

View File

@ -4,20 +4,21 @@ using Godot.Collections;
namespace RPPackage { namespace RPPackage {
public class RPMessage public class RPMessage
{ {
public string Type { get; set; } public string Type { get; set; } = string.Empty;
public string Cmd { get; set; } public string Cmd { get; set; } = string.Empty;
public Dictionary Data { get; set; } public Dictionary? Data { get; set; }
public string Uid { get; set; } public string? Uid { get; set; }
public string Token { get; set; } public string? Token { get; set; }
public string Code { get; set; } public string? Code { get; set; }
public Dictionary ToDictionary() public Dictionary ToDictionary()
{ {
var dict = new Dictionary(); var dict = new Dictionary {
if (Type != null) // if (Type != null)
dict.Add("type", Type); { "type", Type },
if (Cmd != null) // if (Cmd != null)
dict.Add("cmd", Cmd); { "cmd", Cmd }
};
if (Data != null) if (Data != null)
dict.Add("data", Data); dict.Add("data", Data);

View File

@ -2,16 +2,16 @@ using Godot;
using Godot.Collections; using Godot.Collections;
public partial class EventDrivenWebSocket : WebSocketPeer { public partial class EventDrivenWebSocket : WebSocketPeer {
public delegate void WebSocketEventHandler(string eventName, params object[] args); public delegate void WebSocketEventHandler(string eventName, params object[]? args);
public delegate void WSBinMsgEventHandler(byte[] args); public delegate void WSBinMsgEventHandler(byte[] args);
public delegate void WSMsgEventHandler(string args); public delegate void WSMsgEventHandler(string args);
public event WebSocketEventHandler OnOpen; public event WebSocketEventHandler? OnOpen;
public event WSBinMsgEventHandler OnMessage; public event WSBinMsgEventHandler? OnMessage;
public event WSMsgEventHandler OnText; public event WSMsgEventHandler? OnText;
public event WSBinMsgEventHandler OnBinary; public event WSBinMsgEventHandler? OnBinary;
public event WebSocketEventHandler OnClose; public event WebSocketEventHandler? OnClose;
public event WebSocketEventHandler OnError; public event WebSocketEventHandler? OnError;
private bool isConnected = false; private bool isConnected = false;
private bool isCloseEventFired = false; private bool isCloseEventFired = false;
@ -32,7 +32,7 @@ public partial class EventDrivenWebSocket : WebSocketPeer {
} }
public void ConnectToUrlEx(string url, double delayTime = 3, public void ConnectToUrlEx(string url, double delayTime = 3,
TlsOptions tlsClientOptions = null) { TlsOptions? tlsClientOptions = null) {
if (connectingTime >= 0) { if (connectingTime >= 0) {
return; return;
} }

View File

@ -0,0 +1,141 @@
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<MoveRecord> moveRecords = new();
protected readonly int MAX_RECORDS;
public int Rows => rows;
public int Cols => cols;
public event EventHandler<SetPieceEventArgs>? OnSetPiece;
public event EventHandler<IPiece>? OnInsert;
public event EventHandler<IPiece>? OnRemove;
public event EventHandler<MoveEventArgs>? OnMove;
public event EventHandler<MoveRecord>? OnAddRecord;
public event EventHandler<MoveRecord>? 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);
if (piece == null) return null;
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;
}
}
}

View File

@ -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<string, object> 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<string, object> Data {
get => data;
set => data = value;
}
public event EventHandler<Vector2I>? OnPos;
public event EventHandler<Vector2I>? OnMove;
public event EventHandler<bool>? OnSelected;
public event EventHandler<string>? 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();
}
}

View File

@ -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<int>? 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;
}
}

View File

@ -0,0 +1,114 @@
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<IBoard.MoveEventArgs> 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();
// TODO FIXME it can be simple
if (record.From is null || record.To is null) {
return;
}
IPiece? from = record.From is not null ? board.GetPiece(record.From) : null;
IPiece? to = record.To is not null ? board.GetPiece(record.To) : null;
board.AddRecord(from, to, record.From ?? new(), record.To ?? new());
}
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();
}
}

View File

@ -0,0 +1,83 @@
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; } = "unknown";
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 ?? new();
TurnsSide = turnsSide;
OnPos += (sender, args) => {
localPos = Global2Local(Pos);
};
Pos = pos is not null ? Local2Global(pos) : Vector2I.Zero;
localPos = Global2Local(Pos);
}
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<Vector2I> CanMoveAllPosSelf() {
return new List<Vector2I>();
}
public IEnumerable<Vector2I> CanMoveAllPosLocal() {
var self = CanMoveAllPosSelf().Select(item => item + localPos);// 转换局部坐标
var ret = self.Where(item => {
CCPiece? piece = GetCCPieceLocal(item);
bool ret = piece is null || piece.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<Vector2I> 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);
}
}

View File

@ -1,12 +1,13 @@
using Vector2 = Godot.Vector2;
using System; using System;
using System.Collections; using System.Collections;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public class Player { public class Player {
private readonly VirtualBoard board; private readonly CCBoard board;
private readonly SelectedPiece selectedNode; private readonly SelectedPiece selectedNode;
public EventHandler<VirtualBoard.MoveEventArgs> OnMove; public EventHandler<IBoard.MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true; public bool CanMove { get; set; } = true;
public enum PlayerType { public enum PlayerType {
@ -14,16 +15,16 @@ public class Player {
AI AI
} }
public Player(VirtualBoard board, PlayerType type = PlayerType.Human) public Player(CCBoard board, PlayerType type = PlayerType.Human)
{ {
this.board = board; this.board = board;
this.selectedNode = new SelectedPiece(board); this.selectedNode = new SelectedPiece(board);
} }
public void HandleBoardPosClick(Vector2 clickPos) { public void HandleBoardPosClick(Vector2I clickPos) {
if (board.ArrPosOutOfRange(clickPos)) return; if (board.IsPosOutOfRange(clickPos)) return;
// GD.Print($"VirtualBoard {clickPos} clicked"); // Console.WriteLine($"VirtualBoard {clickPos} clicked");
VirtualPiece clickChess = board.GetPiece(clickPos); IPiece? clickChess = board.GetPiece(clickPos);
if (!selectedNode.HasSelected()) { if (!selectedNode.HasSelected()) {
// Select piece // Select piece
@ -42,21 +43,24 @@ public class Player {
} }
} }
public void MoveAndRecord(Vector2 toPos, Vector2 fromPos) { public void MoveAndRecord(Vector2I toPos, Vector2I fromPos) {
// GD.Print($"{fromPos} move to {toPos}"); // GD.Print($"{fromPos} move to {toPos}");
VirtualPiece toChess = board.GetPiece(toPos); IPiece? toChess = board.GetPiece(toPos);
VirtualPiece fromChess = board.GetPiece(fromPos); IPiece? fromChess = board.GetPiece(fromPos);
fromChess?.Selected(false); if (fromChess != null) fromChess.IsSelected = false;
else return;
selectedNode.Clear();
if (!fromChess.CanMove(toPos)) {
return;
}
// MUST BE THERE !!! 防止删除节点后在启动回调导致错误 // MUST BE THERE !!! 防止删除节点后在启动回调导致错误
OnMove?.Invoke(this, new VirtualBoard.MoveEventArgs { From = fromPos, To = toPos }); OnMove?.Invoke(this, new IBoard.MoveEventArgs
{ From = fromPos, To = toPos, Piece = fromChess });;
VirtualPiece NowNode;
if (toChess != null) { if (toChess != null) {
NowNode = toChess;
board.RemovePiece(toPos); board.RemovePiece(toPos);
} else {
NowNode = toChess;
} }
board.MovePiece(fromPos, toPos); board.MovePiece(fromPos, toPos);
@ -73,42 +77,46 @@ public class Player {
private class SelectedPiece { private class SelectedPiece {
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
private Vector2 selectedNodePos = Vector2.Inf; private Vector2I selectedNodePos = Vector2I.MaxValue;
private VirtualPiece piece; private IPiece? piece = null;
private readonly VirtualBoard board; private readonly CCBoard board;
public ArrayList allowedPieces = null; public ArrayList? allowedPieces = null;
public SelectedPiece(VirtualBoard board) { public SelectedPiece(CCBoard board) {
this.board = board; this.board = board;
} }
public void Clear() { public void Clear() {
if (selectedNodePos != Vector2.Inf) { if (selectedNodePos != Vector2I.MaxValue) {
selectedNodePos = Vector2.Inf; selectedNodePos = Vector2I.MaxValue;
piece.Selected(false); if (piece != null)
piece.IsSelected = false;
} }
// Console.WriteLine("SelectedPiece.Clear {0}", piece);
} }
public void SetPos(Vector2 pos) { public void SetPos(Vector2I pos) {
// piece = board.GetNodeFromBoard(pos) as VirtualPiece;
piece = board.GetPiece(pos); piece = board.GetPiece(pos);
if (allowedPieces != null && allowedPieces.Contains(piece) == false) { if (allowedPieces != null && allowedPieces.Contains(piece) == false) {
return; return;
} }
selectedNodePos = pos; selectedNodePos = pos;
piece.Selected(true); if (piece == null)
return;
piece.IsSelected = true;
Console.WriteLine("SelectedPiece.SetPos {0}", piece);
} }
public VirtualPiece GetPiece() { public IPiece? GetPiece() {
return piece; return piece;
} }
public Vector2 GetPos() { public Vector2I GetPos() {
return selectedNodePos; return selectedNodePos;
} }
public bool HasSelected() { public bool HasSelected() {
return selectedNodePos != Vector2.Inf; return selectedNodePos != Vector2I.MaxValue;
} }
} }
} }

View File

@ -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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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 - localPos);
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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> 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

View File

@ -1,20 +0,0 @@
// 帅/将 (General) - 代表双方的最高统帅。
// 子类名ChessGeneral
// 仕/士 (Advisor) - 保护帅/将的近身侍卫。
// 子类名ChessAdvisor
// 相/象 (Elephant) - 行动受限,走田字格,不能过河。
// 子类名ChessElephant
// 車/车 (Chariot) - 横竖移动,威力巨大。
// 子类名ChessChariot
// 馬/马 (Horse) - 走日字形,跳跃式移动。
// 子类名ChessHorse
// 砲/炮 (Cannon) - 需要隔子才能吃子,直线移动。
// 子类名ChessCannon
// 兵/卒 (Pawn) - 最基础的棋子,过河后可横移。
// 子类名ChessPawn

View File

@ -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<VirtualPiece> moveRecords;
public EventHandler<VirtualBoard.MoveEventArgs> 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<VirtualPiece>(
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();
}
}

32
Scripts/Src/IBoard.cs Normal file
View File

@ -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; } = new();
}
public class MoveEventArgs : EventArgs {
public IPiece? Piece { get; set; }
public Vector2I From { get; set; } = new();
public Vector2I To { get; set; } = new();
}
event EventHandler<IPiece> OnInsert;
event EventHandler<IPiece> OnRemove;
event EventHandler<MoveEventArgs> OnMove;
event EventHandler<SetPieceEventArgs> 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();
}

18
Scripts/Src/IPiece.cs Normal file
View File

@ -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<string, object> Data { get; set; }
event EventHandler<Vector2I> OnPos;
event EventHandler<Vector2I> OnMove;
event EventHandler<bool> OnSelected;
event EventHandler<string> OnName;
bool CanMove(Vector2I to);
bool Move(Vector2I pos);
}

133
Scripts/Src/Vector.cs Normal file
View File

@ -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}]";
}
}

View File

@ -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<SetPiecePosEventArgs> OnSetPiecePos;
public event EventHandler<VirtualPiece> OnInsert;
public event EventHandler<VirtualPiece> OnRemove;
public event EventHandler<MoveEventArgs> 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));
}
}
}
}

View File

@ -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<Vector2> OnMove;
public event Action<bool> 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;
}
}

View File

@ -1,63 +0,0 @@
// using System.Numerics;
using Godot;
using System.Collections.Generic;
using System;
public class MoveRecords<T> {
private readonly LinkedList<MoveRecord> records = new LinkedList<MoveRecord>(); // 使用队列替换栈
private readonly int maxRecords; // 记录上限
private readonly Action<T, T, Vector2, Vector2> onAddRecordCallback; // 添加记录时的回调
private readonly Action<T, T, Vector2, Vector2> onUndoRecordCallback; // 撤销记录时的回调
public MoveRecords(
Action<T, T, Vector2, Vector2> onAddRecordCallback = null,
Action<T, T, Vector2, Vector2> 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;
}
}
}

View File

@ -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
);
}

31
Test/Test.csproj Normal file
View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<!-- <ItemGroup>
<ProjectReference Include="..\Chinese_Chess.csproj" />
</ItemGroup> -->
<ItemGroup>
<Compile Include="..\Scripts\Src\*.cs" />
</ItemGroup>
</Project>

16
Test/UnitTest1.cs Normal file
View File

@ -0,0 +1,16 @@
using Vector;
namespace Test;
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}

93
Test/src/TestVector.cs Normal file
View File

@ -0,0 +1,93 @@
using Vector;
namespace Test;
public class TestsVector
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
[Test]
public void TestVector2I_Constructors()
{
// Arrange & Act
var v1 = new Vector2I();
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(v2);
// Assert
Assert.That(v1.X, Is.EqualTo(0));
Assert.That(v1.Y, Is.EqualTo(0));
Assert.That(v2.X, Is.EqualTo(1));
Assert.That(v2.Y, Is.EqualTo(2));
Assert.That(v3.X, Is.EqualTo(1));
Assert.That(v3.Y, Is.EqualTo(2));
}
[Test]
public void TestVector2I_Operators()
{
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var v3 = v1 + v2;
var v4 = v1 - v2;
var v5 = v1 * v2;
// Assert
Assert.That(v3, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v4, Is.EqualTo(new Vector2I(-2, -2)));
Assert.That(v5, Is.EqualTo(new Vector2I(3, 8)));
}
[Test]
public void TestTrans2DI_Constructors()
{
// Arrange & Act
var t1 = new Trans2DI();
var t2 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(0, 0));
var t3 = new Trans2DI(t2);
var t4 = new Trans2DI(1, 0, 0, 1, 0, 0);
// Assert
Assert.That(t1.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t1.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t1.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t2.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t2.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t2.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t3.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t3.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t3.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t4.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t4.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t4.Origin, Is.EqualTo(new Vector2I(0, 0)));
}
[Test]
public void TestTrans2DI_Operators()
{
// Arrange
var t1 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(1, 2));
var v1 = new Vector2I(3, 4);
// Act
var v2 = t1 * v1;
var v3 = v1 * t1;
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v3, Is.EqualTo(new Vector2I(2, 2)));
}
}