Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
e16f76e11f | |||
327c2df94d | |||
9c619784af |
8
.gitignore
vendored
8
.gitignore
vendored
@ -25,3 +25,11 @@ bin/
|
||||
*.sln
|
||||
*.error
|
||||
*.key*
|
||||
.editorconfig
|
||||
|
||||
# cs project files
|
||||
!*.csproj
|
||||
|
||||
# Test files
|
||||
Test/bin
|
||||
Test/obj
|
13
ChessGame.csproj
Normal file
13
ChessGame.csproj
Normal 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>
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#nullable disable
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
117
Scripts/Src/AbstractBoard.cs
Normal file
117
Scripts/Src/AbstractBoard.cs
Normal 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);
|
||||
}
|
||||
}
|
67
Scripts/Src/AbstractPiece.cs
Normal file
67
Scripts/Src/AbstractPiece.cs
Normal 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}";
|
||||
}
|
||||
}
|
89
Scripts/Src/ChineseChess/CCBoard.cs
Normal file
89
Scripts/Src/ChineseChess/CCBoard.cs
Normal 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;
|
||||
}
|
||||
}
|
117
Scripts/Src/ChineseChess/CCMain.cs
Normal file
117
Scripts/Src/ChineseChess/CCMain.cs
Normal 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();
|
||||
}
|
||||
}
|
85
Scripts/Src/ChineseChess/CCPiece.cs
Normal file
85
Scripts/Src/ChineseChess/CCPiece.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
246
Scripts/Src/ChineseChess/CCTypes.cs
Normal file
246
Scripts/Src/ChineseChess/CCTypes.cs
Normal 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
|
@ -1,20 +0,0 @@
|
||||
// 帅/将 (General) - 代表双方的最高统帅。
|
||||
// 子类名:ChessGeneral
|
||||
|
||||
// 仕/士 (Advisor) - 保护帅/将的近身侍卫。
|
||||
// 子类名:ChessAdvisor
|
||||
|
||||
// 相/象 (Elephant) - 行动受限,走田字格,不能过河。
|
||||
// 子类名:ChessElephant
|
||||
|
||||
// 車/车 (Chariot) - 横竖移动,威力巨大。
|
||||
// 子类名:ChessChariot
|
||||
|
||||
// 馬/马 (Horse) - 走日字形,跳跃式移动。
|
||||
// 子类名:ChessHorse
|
||||
|
||||
// 砲/炮 (Cannon) - 需要隔子才能吃子,直线移动。
|
||||
// 子类名:ChessCannon
|
||||
|
||||
// 兵/卒 (Pawn) - 最基础的棋子,过河后可横移。
|
||||
// 子类名:ChessPawn
|
@ -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
33
Scripts/Src/IBoard.cs
Normal 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
22
Scripts/Src/IPiece.cs
Normal 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
133
Scripts/Src/Vector.cs
Normal 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}]";
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
31
Test/Test.csproj
Normal 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
16
Test/UnitTest1.cs
Normal 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
263
Test/src/TestVector.cs
Normal 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)));
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user