Compare commits

...

3 Commits
v0.0.3 ... main

Author SHA1 Message Date
ZZY
e16f76e11f refactor(重构): 重构了事件驱动的代码体系,使用全新命名和版本,以及测试套件的初试
- 移除了.csproj文件
- 更新了.gitignore,添加了.editorconfig
- 重构了IBoard和IPiece接口,引入了新的事件处理机制
- 优化了CCBoard、CCPiece等类的实现,使用新的事件驱动模型
- 删除了冗余代码,提高了代码的可读性和可维护性
2024-11-24 15:42:30 +08:00
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
40 changed files with 1507 additions and 562 deletions

8
.gitignore vendored
View File

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

13
ChessGame.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>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")
[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

View File

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

View File

@ -1,3 +1,4 @@
#nullable disable
using Godot;
using Godot.Collections;
using ChineseChess;
@ -39,21 +40,24 @@ public partial class ChessGame : Node2D {
sideOpposite = ChessCore.TurnsSideType.Red;
}
if (isSession) {
Game = new(ChessCore.Mode.MultiMode, sideSelf);
} else {
Game = new(ChessCore.Mode.SingleMode, sideSelf);
}
board.LoadBoard(Game.board);
Game.InitGame();
// if (isSession) {
// Game = new(ChessCore.Mode.MultiMode, sideSelf);
// } else {
// Game = new(ChessCore.Mode.SingleMode, sideSelf);
// }
Game = new(isSession ? ChessCore.Mode.MultiMode : ChessCore.Mode.SingleMode, sideSelf, board);
Game.Init();
Game.board.OnMove += (sender, e) => {
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 +65,7 @@ public partial class ChessGame : Node2D {
{"id", global.RPClient.GetUserId()}
});
} else {
Game.OnPosClicked(pos);
Game.OnPosClicked(vector);
}
};
@ -99,7 +103,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 +142,7 @@ public partial class ChessGame : Node2D {
{"id", global.RPClient.GetUserId()},
});
} else {
if (Game.board.Steps == 0) board.Clear();
Game.Undo();
}
}
@ -150,6 +156,7 @@ public partial class ChessGame : Node2D {
});
} else {
Game.ReInit();
board.Clear();
}
}
}

View File

@ -1,3 +1,4 @@
#nullable disable
using Godot;
using Godot.Collections;

View File

@ -1,38 +1,72 @@
// Chessboard.cs
using System;
using Godot;
using ChineseChess;
using Godot.Collections;
public partial class ChessBoard : Node2D {
public event EventHandler<Vector2> OnMouseClicked;
public event EventHandler<Vector2> OnPosClicked;
using static IBoard;
public partial class ChessBoard : Node2D, ICCBoardOn {
public event EventHandler<Vector2>? OnMouseClicked;
public event EventHandler<Vector2>? OnPosClicked;
public event EventHandler<int>? OnStepsChanged;
public Vector2 from = Vector2.Inf, to = Vector2.Inf;
public Array<Vector2> canMovePos = [];
public override void _Ready() {
}
public void LoadBoard(VirtualBoard board) {
board.OnRemove += (sender, piece) => {
if (piece.data != null) {
RemoveChild(piece.data as Node);
}
};
board.OnInsert += (sender, piece) => {
if (piece.data != null && piece.data is Node) {
AddChild(piece.data as Node);
} else {
ChessPiece chessPiece = new(piece.name, piece);
if (piece.Pos().Y >= 5) {
chessPiece.LabelColor = new Color("red");
} else {
chessPiece.LabelColor = new Color("black");
}
AddChild(chessPiece);
}
};
public void Clear() {
from = to = Vector2.Inf;
QueueRedraw();
}
public override void _Input(InputEvent @event) {
void IBoardOn.OnInsert(object self, IPiece piece) {
ChessPiece? node = piece.On as ChessPiece;
// throw new InvalidOperationException();
node ??= new ChessPiece((CCPiece)piece);
node.ShowBehindParent = true;
AddChild(node);
// ChessPiece chessPiece = null;
// if (piece.Data.TryGetValue("Godot", out object node)) {
// chessPiece = node as ChessPiece;
// } else {
// chessPiece = new((CCPiece)piece);
// }
// chessPiece.ShowBehindParent = true;
// AddChild(chessPiece);
}
void IBoardOn.OnRemove(object self, IPiece piece) {
if (piece.On is not ChessPiece node) {
throw new InvalidOperationException();
}
RemoveChild(node);
}
void IBoardOn.OnMove(object self, IBoardOn.MoveEventArgs vals) {
from = PosTrans.transArrToPix * new Vector2(vals.From.X, vals.From.Y);
to = PosTrans.transArrToPix * new Vector2(vals.To.X, vals.To.Y);
QueueRedraw();
}
void ICCBoardOn.OnSteps(object _self, int oldSteps) {
if (_self is not CCBoard self) return;
OnStepsChanged?.Invoke(self, self.Steps);
}
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 +75,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
);
}
}

View File

@ -1,34 +1,40 @@
// Chesspiece.cs
using Godot;
using ChineseChess;
using System.Linq;
using System;
public partial class ChessPiece : Sprite2D {
// 文字内容
using static IPiece;
public partial class ChessPiece : Sprite2D, IPieceOn {
[Export]
public string PieceLabel { get; set; } = null;
// 文字颜色(可导出以编辑器调整)
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 Label labelOfChessName;
private readonly VirtualPiece piece;
public VirtualPiece GetVirtualPiece() {
return piece;
void IPieceOn.OnPos(object _self, Vector.Vector2I oldPos) {
if (_self is not CCPiece self) return;
Position = ChessBoard.PosTrans.transArrToPix * new Vector2(self.Pos.X, self.Pos.Y);
}
private void OnMove(Vector2 newPos) {
Position = PosTrans.transArrToPix * new Vector2(newPos.X, newPos.Y);
}
public void OnSelected(bool isSelected) {
if (isSelected) {
GD.Print($"{piece.Pos()} is selected");
void IPieceOn.OnSelected(object _self, bool oldIsSelected) {
if (GetParent() is not ChessBoard chessBoard || _self is not CCPiece self) return;
if (self.IsSelected) {
GD.Print($"{self.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($"{self.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 +43,24 @@ 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;
this.piece = piece;
piece.OnMove += OnMove;
piece.OnSelected += OnSelected;
piece.data = this;
public ChessPiece(CCPiece piece) {
PieceLabel = piece.CNName;
LabelColor = piece.TurnsSide == ChessCore.TurnsSideType.Red ? new Color("red") : new Color("black");
piece.On = this;
// Must Be Call for init Pos
piece.On.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(

View File

@ -1,15 +1,15 @@
#nullable disable
using System.Collections.Generic;
using Godot;
using RPPackage;
public partial class Global : Node
{
public partial class Global : Node {
public RPClientEDWS RPClient = new();
public string sessionId;
public Node CurrentScene { get; set; }
public Theme GlobalTheme = null;
readonly GodotConfigManager GlobalConfig = new("user://config.cfg");
readonly GodotConfigManager GlobalConfig = new("user://config.cfg");
public Dictionary<string, Variant> GlobalConfigDict = new() {
{"font_size", 20},
{"server_url", "wss://game.zzyxyz.com/"},
@ -21,8 +21,7 @@ public partial class Global : Node
};
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
public override void _Ready() {
if (OS.GetName() == "Android") {
bool ret = OS.RequestPermissions();
GD.Print($"RequestPermissions ret is {ret}");
@ -37,41 +36,36 @@ public partial class Global : Node
};
Viewport root = GetTree().Root;
CurrentScene = root.GetChild(root.GetChildCount() - 1);
CurrentScene = root.GetChild(root.GetChildCount() - 1);
GlobalConfig.LoadConfig("Global", GlobalConfigDict);
SetProcess(false);
}
public void ConfigFlush()
{
public void ConfigFlush() {
int font_size = (int)GlobalConfigDict["font_size"];
GlobalTheme.DefaultFontSize = font_size;
// GlobalTheme?.SetFontSize("font_size", "Label", font_size);
// GlobalTheme?.SetFontSize("font_size", "Button", font_size);
// GlobalTheme?.SetFontSize("font_size", "TextEdit", font_size);
// GlobalTheme?.SetFontSize("font_size", "LineEdit", font_size);
// GlobalTheme?.SetFontSize("font_size", "Button", font_size);
// GlobalTheme?.SetFontSize("font_size", "TextEdit", font_size);
// GlobalTheme?.SetFontSize("font_size", "LineEdit", font_size);
// CurrentScene.GetWindow().AddThemeFontSizeOverride("Control", (int)GlobalConfigDict["font_size"]);
}
private void OnGotoScene()
{
private void OnGotoScene() {
ConfigFlush();
}
public void SaveConfig()
{
public void SaveConfig() {
GlobalConfig.SaveConfig("Global", GlobalConfigDict);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
public override void _Process(double delta) {
RPClient.PollEx(delta);
}
public override void _Notification(int what)
{
public override void _Notification(int what) {
if (what == NotificationWMCloseRequest) {
// SaveConfig();
RPClient.Close();
@ -81,8 +75,7 @@ public partial class Global : Node
public delegate void ChangeSceneCallback(Node newSence);
private static ChangeSceneCallback changeSceneCallback = null;
public void GotoScene(string path, ChangeSceneCallback callback = null)
{
public void GotoScene(string path, ChangeSceneCallback callback = null) {
// This function will usually be called from a signal callback,
// or some other function from the current scene.
// Deleting the current scene at this point is
@ -100,8 +93,7 @@ public partial class Global : Node
changeSceneCallback = null;
}
public void DeferredGotoScene(string path, Callable onLoaded)
{
public void DeferredGotoScene(string path, Callable onLoaded) {
// It is now safe to remove the current scene.
CurrentScene.Free();

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,117 @@
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
using static IBoard;
public abstract class AbstractBoard(int rows, int cols, int maxRecords = int.MaxValue,
IBoardOn? on = null) : IBoard {
private readonly int rows = rows;
private readonly int cols = cols;
protected readonly IPiece?[,] pieces = new IPiece[rows, cols];
protected readonly List<MoveRecord> moveRecords = [];
protected readonly int MAX_RECORDS = maxRecords;
public int Rows => rows;
public int Cols => cols;
protected IBoardOn? on = on;
public virtual IBoardOn? On { get => on; set => on = value; }
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 is not null) piece.Pos = pos;
// if (oldPiece != null) oldPiece.Pos = Vector2I.Zero;
on?.OnSetPieceInteral(this, new IBoardOn.SetPieceEventArgs
(oldPiece, piece, pos));
return oldPiece;
}
public virtual bool InsertPiece(IPiece piece, Vector2I pos) {
if (GetPiece(pos) is not null && piece == null) {
return false;
}
on?.OnInsert(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) is not null || piece is null) {
return false;
}
on?.OnMove(this, new IBoardOn.MoveEventArgs(piece, from, to));
SetPiece(null, from);
SetPiece(piece, to);
return true;
}
public virtual IPiece? RemovePiece(Vector2I pos) {
IPiece? piece = GetPiece(pos);
if (piece is null) return null;
on?.OnRemove(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);
on?.OnAddRecord(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 is not null) {
MovePiece(record.ToPos, record.FromPos);
}
// 恢复旧位置的棋子
if (record.To is not null) {
InsertPiece(record.To, record.ToPos);
}
on?.OnUndoRecord(this, record);
}
}

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
using static IPiece;
public abstract class AbstractPiece(string name = "", Vector2I? pos = null,
IPieceOn? on = null) : IPiece {
private Vector2I pos = pos ?? new(); // 注意这个坐标的非像素坐标而是棋盘坐标
private bool isSelected = false;
protected string name = name;
private Dictionary<string, object> data = [];
protected IPieceOn? on = on;
public virtual IPieceOn? On { get => on; set => on = value; }
public virtual Vector2I Pos {
get => pos;
set {
if (pos != value) {
var oldPos = pos;
pos = value;
on?.OnPos(this, oldPos);
}
}
}
public virtual bool IsSelected {
get => isSelected;
set {
if (isSelected != value) {
var oldIsSelected = isSelected;
isSelected = value;
on?.OnSelected(this, oldIsSelected);
}
}
}
public virtual string Name {
get => name;
set {
if (name != value) {
var oldName = name;
name = value;
on?.OnName(this, oldName);
}
}
}
public virtual Dictionary<string, object> Data {
get => data;
set => data = value;
}
public virtual bool Move(Vector2I pos) {
if (!CanMove(pos)) {
return false;
}
Pos = pos;
on?.OnMove(this, pos);
return true;
}
public abstract bool CanMove(Vector2I to);
public override string ToString() {
return $"{Name} at {pos}";
}
}

View File

@ -0,0 +1,89 @@
using System.Collections;
using static ChineseChess.ChessCore;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public interface ICCBoardOn : IBoard.IBoardOn {
void OnSteps(object self, int oldSteps) {}
}
public class CCBoard(ICCBoardOn? on = null) : AbstractBoard(9, 10, on: on) {
private int steps = 0;
protected new ICCBoardOn? on = on;
// public virtual IPieceOn? On { get => on; set => on = value; }
public new ICCBoardOn? On { get => on; set {
on = value; base.on = value;} }
public int Steps {
get => steps;
protected set {
if (steps != value) {
var oldSteps = steps;
steps = value;
on?.OnSteps(this, oldSteps);
}
}
}
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 CCChariot (this, TurnsSideType.Black, new Vector2I( 4, 0)),
new CCHorse (this, TurnsSideType.Black, new Vector2I( 3, 0)),
new CCElephant(this, TurnsSideType.Black, new Vector2I( 2, 0)),
new CCAdvisor (this, TurnsSideType.Black, new Vector2I( 1, 0)),
new CCGeneral (this, TurnsSideType.Black, new Vector2I( 0, 0)),
new CCAdvisor (this, TurnsSideType.Black, new Vector2I(-1, 0)),
new CCElephant(this, TurnsSideType.Black, new Vector2I(-2, 0)),
new CCHorse (this, TurnsSideType.Black, new Vector2I(-3, 0)),
new CCChariot (this, TurnsSideType.Black, new Vector2I(-4, 0)),
new CCCannon (this, TurnsSideType.Black, new Vector2I( 3, 2)),
new CCCannon (this, TurnsSideType.Black, new Vector2I(-3, 2)),
new CCPawn (this, TurnsSideType.Black, new Vector2I(-4, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I(-2, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 0, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 2, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 4, 3)),
]);
ArrayList redPart = InitOnePartPieces(TurnsSideType.Red, [
new CCChariot (this, TurnsSideType.Red, new Vector2I( 4, 0)),
new CCHorse (this, TurnsSideType.Red, new Vector2I( 3, 0)),
new CCElephant(this, TurnsSideType.Red, new Vector2I( 2, 0)),
new CCAdvisor (this, TurnsSideType.Red, new Vector2I( 1, 0)),
new CCGeneral (this, TurnsSideType.Red, new Vector2I( 0, 0)),
new CCAdvisor (this, TurnsSideType.Red, new Vector2I(-1, 0)),
new CCElephant(this, TurnsSideType.Red, new Vector2I(-2, 0)),
new CCHorse (this, TurnsSideType.Red, new Vector2I(-3, 0)),
new CCChariot (this, TurnsSideType.Red, new Vector2I(-4, 0)),
new CCCannon (this, TurnsSideType.Red, new Vector2I( 3, 2)),
new CCCannon (this, TurnsSideType.Red, new Vector2I(-3, 2)),
new CCPawn (this, TurnsSideType.Red, new Vector2I(-4, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I(-2, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 0, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 2, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 4, 3)),
]);
return (blackPart, redPart);
}
private ArrayList InitOnePartPieces(TurnsSideType side, CCPiece[] pieces) {
ArrayList list = [];
foreach (var piece in pieces) {
list.Add(piece);
InsertPiece(piece, piece.Pos);
}
return list;
}
}

View File

@ -0,0 +1,117 @@
using Vector2 = Vector.Vector2I;
using System;
using System.Collections;
using static IBoard.IBoardOn;
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;
private readonly Player playerSelf;
private readonly Player playerOpponent;
// public EventHandler<IBoard.MoveEventArgs> OnMove;
public ChessCore(Mode mode, TurnsSideType selfSide, ICCBoardOn? boardOn = null) {
this.selfSide = selfSide;
board = new(boardOn);
playerSelf = new(board, Player.PlayerType.Human);
playerOpponent = new(board, Player.PlayerType.Human);
void func(object? _self, 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,85 @@
namespace ChineseChess;
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;
Pos = pos is not null ? Local2Global(pos) : Vector2I.Zero;
}
public override Vector2I Pos {
get => base.Pos; set {
base.Pos = value;
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 [];
}
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,14 @@
using Vector2 = Godot.Vector2;
using System;
using System.Collections;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
using static IBoard.IBoardOn;
public class Player {
private readonly VirtualBoard board;
private readonly CCBoard board;
private readonly SelectedPiece selectedNode;
public EventHandler<VirtualBoard.MoveEventArgs> OnMove;
public EventHandler<MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true;
public enum PlayerType {
@ -14,16 +16,15 @@ 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,25 @@ 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;
else return;
// 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 MoveEventArgs
(fromChess, fromPos, toPos));
if (toChess != null) {
board.RemovePiece(toPos);
}
board.MovePiece(fromPos, toPos);
selectedNode.Clear();
@ -73,42 +77,46 @@ 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;
public ArrayList allowedPieces = null;
private Vector2I selectedNodePos = Vector2I.MaxValue;
private IPiece? piece = null;
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;
if (piece != null)
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);
if (piece == null)
return;
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;
}
}
}

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

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

@ -0,0 +1,33 @@
using System;
using Godot;
using Vector2I = Vector.Vector2I;
public interface IBoard {
public interface IBoardOn {
void OnInsert(object self, IPiece piece) {}
void OnRemove(object self, IPiece piece) {}
void OnMove(object self, MoveEventArgs args) {}
void OnAddRecord(object self, MoveRecord record) {}
void OnUndoRecord(object self, MoveRecord record) {}
void OnSetPieceInteral(object self, SetPieceEventArgs args) {}
record SetPieceEventArgs(IPiece? OldPiece, IPiece? NewPiece, Vector2I Pos);
record MoveEventArgs(IPiece? Piece, Vector2I From, Vector2I To);
}
public IBoardOn? On { get; protected set; }
public int Rows { get; }
public int Cols { get; }
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(bool clearRecords);
void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos);
void UndoRecord();
record MoveRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos);
}

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

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
public interface IPiece {
public interface IPieceOn {
void OnPos(object self, Vector2I oldPos) {}
void OnMove(object self, Vector2I oldPos) {}
void OnSelected(object self, bool oldIsSelected) {}
void OnName(object self, string oldName) {}
}
IPieceOn? On { get; protected set; }
Vector2I Pos { get; set; }
string Name { get; set; }
bool IsSelected { get; set; }
Dictionary<string, object> Data { get; set; }
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="..\*.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();
}
}

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

@ -0,0 +1,263 @@
using Vector;
namespace Test.Vector;
public class TestsVector2I {
[SetUp]
public void Setup() {
}
[Test]
public void TestVector2I_Constructors() {
// Arrange & Act
var v1 = new Vector2I();
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(v2);
// Assert
Assert.Multiple(() => {
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.Multiple(() => {
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 TestVector2I_DotProduct() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var dotProduct = v1.Dot(v2);
// Assert
Assert.That(dotProduct, Is.EqualTo(11));
}
[Test]
public void TestVector2I_ParameterModification() {
// 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.Multiple(() => {
Assert.That(v1, Is.EqualTo(new Vector2I(1, 2)), "v1 should not be modified");
Assert.That(v2, Is.EqualTo(new Vector2I(3, 4)), "v2 should not be modified");
});
}
[Test]
public void TestVector2I_Equality() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(3, 4);
// Act & Assert
Assert.Multiple(() => {
Assert.That(v1, Is.EqualTo(v2));
Assert.That(v1, Is.Not.EqualTo(v3));
});
}
[Test]
public void TestVector2I_HashCode() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(3, 4);
// Act & Assert
Assert.Multiple(() => {
Assert.That(v1.GetHashCode(), Is.EqualTo(v2.GetHashCode()));
Assert.That(v1.GetHashCode(), Is.Not.EqualTo(v3.GetHashCode()));
});
}
[Test]
public void TestVector2I_BoundaryValues() {
// Arrange
var v1 = new Vector2I(0, 0);
var v2 = new Vector2I(-1, -2);
var v3 = new Vector2I(int.MaxValue, int.MaxValue);
var v4 = new Vector2I(int.MinValue, int.MinValue);
// Act
var v5 = v1 + v2;
var v6 = v1 - v2;
var v7 = v1 * v2;
var v8 = v3 + v4;
var v9 = v3 - v4;
var v10 = v3 * v4;
// Assert
Assert.Multiple(() => {
Assert.That(v5, Is.EqualTo(new Vector2I(-1, -2)));
Assert.That(v6, Is.EqualTo(new Vector2I(1, 2)));
Assert.That(v7, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(v8, Is.EqualTo(new Vector2I(-1, -1)));
// Assert.That(v9, Is.EqualTo(new Vector2I(2 * int.MaxValue - 1, 2 * int.MaxValue - 1)));
// Assert.That(v10, Is.EqualTo(new Vector2I(int.MaxValue * int.MinValue, int.MaxValue * int.MinValue)));
});
}
}
public class TestsTrans2DI {
[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)));
}
[Test]
public void TestTrans2DI_BoundaryValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(int.MaxValue, 0), new Vector2I(0, int.MaxValue), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(int.MinValue, 0), new Vector2I(0, int.MinValue), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(int.MaxValue, int.MaxValue)));
Assert.That(v3, Is.EqualTo(new Vector2I(int.MinValue, int.MinValue)));
}
[Test]
public void TestTrans2DI_SpecialValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(0, 0), new Vector2I(0, 0), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(v3, Is.EqualTo(new Vector2I(1, 1)));
}
[Test]
public void TestTrans2DI_Multiplication() {
// Arrange
var t1 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(1, 2));
var t2 = new Trans2DI(new Vector2I(2, 0), new Vector2I(0, 2), new Vector2I(3, 4));
// Act
var t3 = t1 * t2;
// Assert
Assert.That(t3.XAxis, Is.EqualTo(new Vector2I(2, 0)));
Assert.That(t3.YAxis, Is.EqualTo(new Vector2I(0, 2)));
Assert.That(t3.Origin, Is.EqualTo(new Vector2I(5, 8)));
}
}

View File

@ -10,10 +10,11 @@ config_version=5
[application]
config/name="Chinese_Chess"
config/name="ChessGame"
config/version="0.0.5"
run/main_scene="res://Main.tscn"
config/features=PackedStringArray("4.3", "C#", "Mobile")
config/icon="res://icon.svg"
config/icon="res://Asserts/icon.svg"
[autoload]
@ -28,7 +29,12 @@ window/handheld/orientation=1
[dotnet]
project/assembly_name="Chinese_Chess"
project/assembly_name="ChessGame"
[navigation]
2d/name_localized={}
2d/version="0.0."
[rendering]