Compare commits

..

No commits in common. "main" and "v0.0.3b" have entirely different histories.

41 changed files with 567 additions and 1616 deletions

8
.gitignore vendored
View File

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

View File

@ -1,13 +0,0 @@
<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,11 +12,7 @@ visible = false
script = ExtResource("3_26g24") script = ExtResource("3_26g24")
[node name="Layer0" type="TileMapLayer" parent="."] [node name="Layer0" type="TileMapLayer" parent="."]
show_behind_parent = true use_parent_material = true
position = Vector2(-8, 184) position = Vector2(-8, 184)
tile_map_data = PackedByteArray("AAAAAPv/JAAEAAEAAAD+//v/JAABAAIAAAACAPv/JAABAAIAABAAAPn/JAABAAIAAFD+//n/JAABAAEAAAACAPn/JAABAAEAAFAAAP3/JAAEAAAAACD+//3/JAACAAEAACACAP3/JAACAAEAADAEAP3/JAADAAAAACAGAP3/JAADAAAAACAIAP3/JAAAAAEAACD8//3/JAADAAAAACD6//3/JAADAAAAACD4//3/JAAAAAEAADAEAPv/JAACAAAAAAAGAPv/JAACAAAAAAAEAPn/JAACAAAAAAD8//v/JAACAAAAAAD8//n/JAACAAAAAAD6//v/JAACAAAAAAD6//f/JAACAAAAAAD+//f/JAACAAAAAAACAPf/JAACAAAAAAAGAPf/JAACAAAAAAAAAPX/JAADAAAAAAACAPX/JAADAAAAAAAEAPX/JAADAAAAAAAGAPX/JAADAAAAAAD+//X/JAADAAAAAAD8//X/JAADAAAAAAD6//X/JAADAAAAAAAIAPn/JAADAAAAAFD4//v/JAADAAAAAED4//n/JAADAAAAAED4//X/JAAAAAEAAGAIAPX/JAAAAAEAAHD6//n/JAADAAEAAAAGAPn/JAADAAEAAAAIAPv/JAADAAAAAFD8//f/JAADAAEAAAAAAPf/JAADAAEAAAAEAPf/JAADAAEAAAAIAPf/JQAAAAAAAFD4//f/JQAAAAAAAED4//P/JAAAAAEAAED6//P/JAADAAAAACD8//P/JAADAAAAACD+//P/JAADAAAAACAAAPP/JAADAAAAACACAPP/JAADAAAAACAEAPP/JAADAAAAACAGAPP/JAADAAAAACAIAPP/JAAAAAEAAFD4//H/JQAAAAAAAGD6//H/JAACAAAAACD8//H/JAADAAEAACD+//H/JAACAAAAACAAAPH/JAADAAEAACACAPH/JAACAAAAACAEAPH/JAADAAEAACAGAPH/JAACAAAAACAIAPH/JQAAAAAAAHD4/+//JAADAAAAAGD6/+//JAADAAEAACD8/+//JAACAAAAACD+/+//JAABAAEAACAAAO//JAABAAIAAHACAO//JAABAAEAAHAEAO//JAACAAAAACAGAO//JAADAAEAACAIAO//JAADAAAAAHD4/+3/JAADAAAAAGD6/+3/JAACAAAAACD8/+3/JAACAAAAACD+/+3/JAABAAIAACAAAO3/JAAEAAEAACACAO3/JAABAAIAADAEAO3/JAACAAAAACAGAO3/JAACAAAAACAIAO3/JAADAAAAAHD4/+v/JAAAAAEAABD6/+v/JAADAAAAAAD8/+v/JAADAAAAAAD+/+v/JAACAAEAAAAAAOv/JAAEAAAAAAACAOv/JAACAAEAABAEAOv/JAADAAAAAAAGAOv/JAADAAAAAAAIAOv/JAAAAAEAAAA=") tile_map_data = PackedByteArray("AAAAAPv/JAAEAAEAAAD+//v/JAABAAIAAAACAPv/JAABAAIAABAAAPn/JAABAAIAAFD+//n/JAABAAEAAAACAPn/JAABAAEAAFAAAP3/JAAEAAAAACD+//3/JAACAAEAACACAP3/JAACAAEAADAEAP3/JAADAAAAACAGAP3/JAADAAAAACAIAP3/JAAAAAEAACD8//3/JAADAAAAACD6//3/JAADAAAAACD4//3/JAAAAAEAADAEAPv/JAACAAAAAAAGAPv/JAACAAAAAAAEAPn/JAACAAAAAAD8//v/JAACAAAAAAD8//n/JAACAAAAAAD6//v/JAACAAAAAAD6//f/JAACAAAAAAD+//f/JAACAAAAAAACAPf/JAACAAAAAAAGAPf/JAACAAAAAAAAAPX/JAADAAAAAAACAPX/JAADAAAAAAAEAPX/JAADAAAAAAAGAPX/JAADAAAAAAD+//X/JAADAAAAAAD8//X/JAADAAAAAAD6//X/JAADAAAAAAAIAPn/JAADAAAAAFD4//v/JAADAAAAAED4//n/JAADAAAAAED4//X/JAAAAAEAAGAIAPX/JAAAAAEAAHD6//n/JAADAAEAAAAGAPn/JAADAAEAAAAIAPv/JAADAAAAAFD8//f/JAADAAEAAAAAAPf/JAADAAEAAAAEAPf/JAADAAEAAAAIAPf/JQAAAAAAAFD4//f/JQAAAAAAAED4//P/JAAAAAEAAED6//P/JAADAAAAACD8//P/JAADAAAAACD+//P/JAADAAAAACAAAPP/JAADAAAAACACAPP/JAADAAAAACAEAPP/JAADAAAAACAGAPP/JAADAAAAACAIAPP/JAAAAAEAAFD4//H/JQAAAAAAAGD6//H/JAACAAAAACD8//H/JAADAAEAACD+//H/JAACAAAAACAAAPH/JAADAAEAACACAPH/JAACAAAAACAEAPH/JAADAAEAACAGAPH/JAACAAAAACAIAPH/JQAAAAAAAHD4/+//JAADAAAAAGD6/+//JAADAAEAACD8/+//JAACAAAAACD+/+//JAABAAEAACAAAO//JAABAAIAAHACAO//JAABAAEAAHAEAO//JAACAAAAACAGAO//JAADAAEAACAIAO//JAADAAAAAHD4/+3/JAADAAAAAGD6/+3/JAACAAAAACD8/+3/JAACAAAAACD+/+3/JAABAAIAACAAAO3/JAAEAAEAACACAO3/JAABAAIAADAEAO3/JAACAAAAACAGAO3/JAACAAAAACAIAO3/JAADAAAAAHD4/+v/JAAAAAEAABD6/+v/JAADAAAAAAD8/+v/JAADAAAAAAD+/+v/JAACAAEAAAAAAOv/JAAEAAAAAAACAOv/JAACAAEAABAEAOv/JAADAAAAAAAGAOv/JAADAAAAAAAIAOv/JAAAAAEAAAA=")
tile_set = ExtResource("1_ws3cq") tile_set = ExtResource("1_ws3cq")
collision_enabled = false
collision_visibility_mode = 2
navigation_enabled = false
navigation_visibility_mode = 2

View File

@ -113,7 +113,6 @@ size_flags_horizontal = 3
[node name="Button" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer"] [node name="Button" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
disabled = true
text = "Clear Config" text = "Clear Config"
[node name="MarginContainer2" type="MarginContainer" parent="BoxContainer/HBoxContainer"] [node name="MarginContainer2" type="MarginContainer" parent="BoxContainer/HBoxContainer"]
@ -122,7 +121,6 @@ size_flags_horizontal = 3
[node name="Button2" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer2"] [node name="Button2" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer2"]
layout_mode = 2 layout_mode = 2
disabled = true
text = "Clear User Data" text = "Clear User Data"
[node name="HFlowContainer" type="HFlowContainer" parent="BoxContainer"] [node name="HFlowContainer" type="HFlowContainer" parent="BoxContainer"]
@ -131,34 +129,23 @@ layout_mode = 2
[node name="Button" type="Button" parent="BoxContainer/HFlowContainer"] [node name="Button" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
disabled = true
text = "GetCacheDir" text = "GetCacheDir"
[node name="Button2" type="Button" parent="BoxContainer/HFlowContainer"] [node name="Button2" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
disabled = true
text = "GetConfigDir" text = "GetConfigDir"
[node name="Button3" type="Button" parent="BoxContainer/HFlowContainer"] [node name="Button3" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
disabled = true
text = "GetDataDir" text = "GetDataDir"
[node name="Button4" type="Button" parent="BoxContainer/HFlowContainer"] [node name="Button4" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
disabled = true
text = "GetUserDataDir" text = "GetUserDataDir"
[node name="MarginContainer8" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="ColorPickerButton" type="ColorPickerButton" parent="BoxContainer/MarginContainer8"]
layout_mode = 2
text = "Trace Color"
[connection signal="pressed" from="BoxContainer/MarginContainer/Back" to="." method="OnBack"] [connection signal="pressed" from="BoxContainer/MarginContainer/Back" to="." method="OnBack"]
[connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"] [connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"]
[connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit" to="." method="OnServerUrlChanged"] [connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit" to="." method="OnServerUrlChanged"]

View File

@ -1,28 +1,29 @@
#nullable disable using System.Collections;
using Godot; using Godot;
using Godot.Collections; using Godot.Collections;
using ChineseChess;
public partial class ChessGame : Node2D { public partial class ChessGame : Node2D {
ChessBoard board; ChessBoard board;
Global global; Global global;
ConfirmationDialog dialog; ConfirmationDialog dialog;
private bool isSession = false; private bool isSession = false;
ChessCore Game;
ChessCore.TurnsSideType sideSelf; private Player playerSelf;
ChessCore.TurnsSideType sideOpposite; private Player playerOpponent;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() { public override void _Ready()
{
// Init.Call(); // Init.Call();
global = GetNode<Global>("/root/Global"); global = GetNode<Global>("/root/Global");
board = GetNode<ChessBoard>("Chessboard"); board = GetNode<ChessBoard>("Chessboard");
isSession = global.RPClient.IsOnline(); isSession = global.RPClient.IsOnline();
playerSelf = new Player(board.board, Player.PlayerType.Human);
playerOpponent = new Player(board.board, Player.PlayerType.Human);
InitChessBoard();
GetNode<LineEdit>("Control/VBoxContainer/MarginContainer3/HFlowContainer/LineEdit") GetNode<LineEdit>("Control/VBoxContainer/MarginContainer3/HFlowContainer/LineEdit")
.Text = global.GlobalData["player_color"].AsString(); .Text = global.GlobalData["player_color"].AsString();
LineEdit turnSideEdit = GetNode<LineEdit>("Control/VBoxContainer/MarginContainer3/HFlowContainer/LineEdit2");
turnSideEdit.Text = "red";
GD.PrintErr("ChessGame ", global.RPClient.GetUserId(), ":",global.GlobalData["player_color"]); GD.PrintErr("ChessGame ", global.RPClient.GetUserId(), ":",global.GlobalData["player_color"]);
dialog = new ConfirmationDialog { dialog = new ConfirmationDialog {
@ -32,40 +33,18 @@ public partial class ChessGame : Node2D {
}; };
AddChild(dialog); AddChild(dialog);
if (global.GlobalData["player_color"].AsString() == "red") {
sideSelf = ChessCore.TurnsSideType.Red;
sideOpposite = ChessCore.TurnsSideType.Black;
} else {
sideSelf = ChessCore.TurnsSideType.Black;
sideOpposite = ChessCore.TurnsSideType.Red;
}
// if (isSession) {
// Game = new(ChessCore.Mode.MultiMode, sideSelf);
// } else {
// Game = new(ChessCore.Mode.SingleMode, sideSelf);
// }
Game = new(isSession ? ChessCore.Mode.MultiMode : ChessCore.Mode.SingleMode, sideSelf, board);
Game.Init();
board.OnStepsChanged += (sender, e) => {
turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black";
};
board.OnPosClicked += (sender, pos) => { board.OnPosClicked += (sender, pos) => {
int posX = (int)pos.X;
int posY = (int)pos.Y;
Vector.Vector2I vector = new(posX, posY);
if (isSession) { if (isSession) {
Game.OnPosClicked(vector, ChessCore.PlayerSideType.Self);
var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary { var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary {
{"type", "mouseClicked"}, {"type", "mouseClicked"},
{"X", pos.X}, {"X", pos.X},
{"Y", pos.Y}, {"Y", pos.Y},
{"id", global.RPClient.GetUserId()} {"id", global.RPClient.GetUserId()}
}); });
playerSelf.HandleBoardPosClick(pos);
} else { } else {
Game.OnPosClicked(vector); playerSelf.HandleBoardPosClick(pos);
playerSelf.SetAllowedPieces(null);
} }
}; };
@ -86,6 +65,44 @@ public partial class ChessGame : Node2D {
public override void _Process(double delta) { public override void _Process(double delta) {
} }
public void InitChessBoard() {
ArrayList black = InitializePieces("black", 0, new[] {
("车", 0, 0), ("马", 1, 0), ("象", 2, 0),
("士", 3, 0), ("将", 4, 0), ("士", 5, 0),
("象", 6, 0), ("马", 7, 0), ("车", 8, 0),
("炮", 1, 2), ("炮", 7, 2),
("卒", 0, 3), ("卒", 2, 3), ("卒", 4, 3), ("卒", 6, 3), ("卒", 8, 3)
});
ArrayList red = InitializePieces("red", 9, new[] {
("车", 0, -0), ("马", 1, -0), ("象", 2, -0),
("士", 3, -0), ("将", 4, -0), ("士", 5, -0),
("象", 6, -0), ("马", 7, -0), ("车", 8, -0),
("炮", 1, -2), ("炮", 7, -2),
("卒", 0, -3), ("卒", 2, -3), ("卒", 4, -3), ("卒", 6, -3), ("卒", 8, -3)
});
if (global.GlobalData["player_color"].AsString() == "red") {
playerSelf.SetAllowedPieces(red);
playerOpponent.SetAllowedPieces(black);
} else {
playerSelf.SetAllowedPieces(black);
playerOpponent.SetAllowedPieces(red);
}
}
private ArrayList InitializePieces(string color, int baseY, (string label, int x, int y)[] positions) {
ArrayList list = new();
foreach (var (label, x, y) in positions) {
ChessPiece piece = new ChessPiece {
PieceLabel = label,
LabelColor = new Color(color)
};
list.Add(piece.GetVirtualPiece());
board.InsertNode(piece, new Vector2(x, baseY + y));
}
return list;
}
private void SessionMsgHandle(Dictionary msg) { private void SessionMsgHandle(Dictionary msg) {
GD.PrintErr($"session msg: {msg}"); GD.PrintErr($"session msg: {msg}");
switch (msg["type"].AsString()) { switch (msg["type"].AsString()) {
@ -103,18 +120,25 @@ public partial class ChessGame : Node2D {
} }
Vector2 mouseClicked = new(GD.StrToVar(msg["X"].ToString()).AsInt32(), Vector2 mouseClicked = new(GD.StrToVar(msg["X"].ToString()).AsInt32(),
GD.StrToVar(msg["Y"].ToString()).AsInt32()); GD.StrToVar(msg["Y"].ToString()).AsInt32());
Vector.Vector2I vector = new((int)mouseClicked.X, (int)mouseClicked.Y); playerOpponent.HandleBoardPosClick(mouseClicked);
Game.OnPosClicked(vector, ChessCore.PlayerSideType.Opponent);
break; break;
case "undo": case "undo":
Game.Undo(); if (msg["id"].ToString() == global.RPClient.GetUserId()) {
break;
}
playerOpponent.Undo();
break; break;
case "reInit": case "reInit":
Game.ReInit(); _ReInit();
break; break;
} }
} }
private void _ReInit() {
playerSelf.ReInit();
InitChessBoard();
}
private void BtnOver() { private void BtnOver() {
GD.Print($"BtnOver {isSession}"); GD.Print($"BtnOver {isSession}");
if (isSession == false) { if (isSession == false) {
@ -137,13 +161,13 @@ public partial class ChessGame : Node2D {
GD.Print($"Undo {isSession}"); GD.Print($"Undo {isSession}");
if (isSession) { if (isSession) {
playerSelf.Undo();
global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{ global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{
{"type", "undo"}, {"type", "undo"},
{"id", global.RPClient.GetUserId()}, {"id", global.RPClient.GetUserId()},
}); });
} else { } else {
if (Game.board.Steps == 0) board.Clear(); playerSelf.Undo();
Game.Undo();
} }
} }
@ -155,8 +179,7 @@ public partial class ChessGame : Node2D {
{"type", "reInit"}, {"type", "reInit"},
}); });
} else { } else {
Game.ReInit(); _ReInit();
board.Clear();
} }
} }
} }

View File

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

View File

@ -1,87 +1,53 @@
// Chessboard.cs // Chessboard.cs
using System; using System;
using Godot; using Godot;
using ChineseChess;
using Godot.Collections;
using static IBoard; public partial class ChessBoard : Node2D {
public partial class ChessBoard : Node2D, ICCBoardOn { public VirtualBoard board = null;
public event EventHandler<Vector2>? OnMouseClicked; public Player playerSelf = null;
public event EventHandler<Vector2>? OnPosClicked;
public event EventHandler<int>? OnStepsChanged;
public Vector2 from = Vector2.Inf, to = Vector2.Inf; public delegate bool ChessMoveFunc(Vector2 toPos, Vector2 fromPos);
public Array<Vector2> canMovePos = []; // public Callable chessMoveFunc { get; set; }
public event EventHandler<Vector2> OnMouseClicked;
public event EventHandler<Vector2> OnPosClicked;
public override void _Ready() { public override void _Ready() {
board = new VirtualBoard(9, 10);
playerSelf = new Player(board);
board.OnRemove += (sender, piece) => {
if (piece.data != null) {
RemoveChild(piece.data as Node);
}
};
board.OnInsert += (sender, piece) => {
if (piece.data != null) {
AddChild(piece.data as Node);
}
};
// board.OnMove += (sender, args) => {
// // chessMoveFunc.Call(args.To, args.From);
// };
} }
public void Clear() { public override void _Input(InputEvent @event) {
from = to = Vector2.Inf;
QueueRedraw();
}
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 && if (@event is InputEventMouseButton mouseEvent &&
mouseEvent.Pressed && mouseEvent.Pressed &&
mouseEvent.ButtonIndex == MouseButton.Left) { mouseEvent.ButtonIndex == MouseButton.Left) {
// HandleMouseClick(GetGlobalTransformWithCanvas().AffineInverse() * mouseButton.Position);
OnMouseClicked?.Invoke(this, GetLocalMousePosition()); OnMouseClicked?.Invoke(this, GetLocalMousePosition());
OnPosClicked?.Invoke(this, (PosTrans.transArrToPix.AffineInverse() * OnPosClicked?.Invoke(this, (PosTrans.transArrToPix.AffineInverse() *
GetLocalMousePosition()).Round()); GetLocalMousePosition()).Round());
} }
} }
public static class PosTrans { public void InsertNode(ChessPiece node, Vector2 arrayPos) {
public static readonly int pixGripSize = 32; AddChild(node);
public static readonly Transform2D transArrToPix = new( VirtualPiece piece = node.GetVirtualPiece();
new Vector2(pixGripSize, 0), // piece.Move(vector);
new Vector2(0, pixGripSize), board.SetPiecePos(piece, arrayPos);
new Vector2(-4, -4.5f) * pixGripSize
);
} }
} }

View File

@ -1,40 +1,34 @@
// Chesspiece.cs // Chesspiece.cs
using Godot; using Godot;
using ChineseChess;
using System.Linq;
using System;
using static IPiece; public partial class ChessPiece : Sprite2D {
public partial class ChessPiece : Sprite2D, IPieceOn { // 文字内容
[Export] [Export]
public string? PieceLabel { get; set; } = null; public string PieceLabel { get; set; } = null;
// Text Color (Can Export for Editor Adjust) // 文字颜色(可导出以编辑器调整)
[Export] [Export]
public Color LabelColor { get; set; } = new Color("white"); public Color LabelColor { get; set; } = new Color("black");
private Vector2 textureSize; private Vector2 textureSize;
private Label? labelOfChessName;
void IPieceOn.OnPos(object _self, Vector.Vector2I oldPos) { private Label labelOfChessName;
if (_self is not CCPiece self) return; private readonly VirtualPiece piece;
Position = ChessBoard.PosTrans.transArrToPix * new Vector2(self.Pos.X, self.Pos.Y);
public VirtualPiece GetVirtualPiece() {
return piece;
} }
void IPieceOn.OnSelected(object _self, bool oldIsSelected) { private void OnMove(Vector2 newPos) {
if (GetParent() is not ChessBoard chessBoard || _self is not CCPiece self) return; Position = PosTrans.transArrToPix * new Vector2(newPos.X, newPos.Y);
if (self.IsSelected) { }
GD.Print($"{self.Pos} is selected");
public void OnSelected(bool isSelected) {
if (isSelected) {
GD.Print($"{piece.Pos()} is selected");
Transform *= transToSeleted; Transform *= transToSeleted;
foreach (var item in self.CanMoveAllPos()) {
chessBoard.canMovePos.Add(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y));
}
} else { } else {
GD.Print($"{self.Pos} is deselected"); GD.Print($"{piece.Pos()} is deselected");
Transform *= transToSeleted.AffineInverse(); Transform *= transToSeleted.AffineInverse();
foreach (var item in self.CanMoveAllPos()) {
chessBoard.canMovePos.Remove(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y));
}
} }
chessBoard.QueueRedraw();
} }
Transform2D transToSeleted = new( Transform2D transToSeleted = new(
@ -43,24 +37,24 @@ public partial class ChessPiece : Sprite2D, IPieceOn {
new Vector2(0, 0) new Vector2(0, 0)
); );
public ChessPiece() : this(new CCPiece()) { public ChessPiece() : this("", Vector2.Zero){
} }
public ChessPiece(CCPiece piece) { public ChessPiece(string name, Vector2 pos) {
PieceLabel = piece.CNName; PieceLabel = name;
LabelColor = piece.TurnsSide == ChessCore.TurnsSideType.Red ? new Color("red") : new Color("black"); piece = new VirtualPiece(name, pos);
piece.On = this; piece.OnMove += OnMove;
// Must Be Call for init Pos piece.OnSelected += OnSelected;
piece.On.OnPos(piece, piece.Pos); piece.data = this;
piece.Data.TryAdd("Godot", this);
} }
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() { public override void _Ready() {
InitLabel(); InitLabel();
} }
private void InitLabel() { private void InitLabel() {
// this.Texture.ResourcePath = "res://Asserts/ChesspieceBase.tres";
Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres"); Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres");
textureSize = Texture.GetSize(); textureSize = Texture.GetSize();
Vector2 labalPosition = new( Vector2 labalPosition = new(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,117 +0,0 @@
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

@ -1,67 +0,0 @@
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

@ -1,89 +0,0 @@
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

@ -1,117 +0,0 @@
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

@ -1,85 +0,0 @@
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,122 +0,0 @@
using System;
using System.Collections;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
using static IBoard.IBoardOn;
public class Player {
private readonly CCBoard board;
private readonly SelectedPiece selectedNode;
public EventHandler<MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true;
public enum PlayerType {
Human,
AI
}
public Player(CCBoard board, PlayerType type = PlayerType.Human) {
this.board = board;
this.selectedNode = new SelectedPiece(board);
}
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
if (clickChess == null) {
// selectedNode.Clear();
return;
}
selectedNode.SetPos(clickPos);
} else if (clickChess == selectedNode.GetPiece()) {
// Unselect piece
selectedNode.Clear();
} else {
// Move piece
// GD.Print("default MoveFunc Move: ", selectedNode.GetPos(), "->", clickPos);
if (CanMove) MoveAndRecord(clickPos, selectedNode.GetPos());
}
}
public void MoveAndRecord(Vector2I toPos, Vector2I fromPos) {
// GD.Print($"{fromPos} move to {toPos}");
IPiece? toChess = board.GetPiece(toPos);
IPiece? fromChess = board.GetPiece(fromPos);
if (fromChess != null) fromChess.IsSelected = false;
else return;
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();
}
public void SelectedClear() {
selectedNode.Clear();
}
public void SetAllowedPieces(ArrayList allowedPieces) {
selectedNode.allowedPieces = allowedPieces;
}
private class SelectedPiece {
// Called when the node enters the scene tree for the first time.
private Vector2I selectedNodePos = Vector2I.MaxValue;
private IPiece? piece = null;
private readonly CCBoard board;
public ArrayList? allowedPieces = null;
public SelectedPiece(CCBoard board) {
this.board = board;
}
public void Clear() {
if (selectedNodePos != Vector2I.MaxValue) {
selectedNodePos = Vector2I.MaxValue;
if (piece != null)
piece.IsSelected = false;
}
// Console.WriteLine("SelectedPiece.Clear {0}", piece);
}
public void SetPos(Vector2I pos) {
piece = board.GetPiece(pos);
if (allowedPieces != null && allowedPieces.Contains(piece) == false) {
return;
}
selectedNodePos = pos;
if (piece == null)
return;
piece.IsSelected = true;
Console.WriteLine("SelectedPiece.SetPos {0}", piece);
}
public IPiece? GetPiece() {
return piece;
}
public Vector2I GetPos() {
return selectedNodePos;
}
public bool HasSelected() {
return selectedNodePos != Vector2I.MaxValue;
}
}
}

View File

@ -1,246 +0,0 @@
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

View File

View File

View File

View File

View File

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

View File

View File

@ -0,0 +1,133 @@
using Vector2 = Godot.Vector2;
using System;
using System.Collections;
public class Player {
private readonly VirtualBoard board;
private readonly SelectedPiece selectedNode;
private readonly MoveRecords<VirtualPiece> moveRecords;
public EventHandler<VirtualBoard.MoveEventArgs> OnMove;
public enum PlayerType {
Human,
AI
}
public Player(VirtualBoard board, PlayerType type = PlayerType.Human)
{
this.board = board;
this.selectedNode = new SelectedPiece(board);
this.moveRecords = new MoveRecords<VirtualPiece>(onUndoRecordCallback: (newNode, oldNode, newPos, oldPos) => {
// GD.Print("Undo: ", newNode, "->", oldNode, ":", newPos, "->", oldPos);
VirtualPiece newPiece = newNode;
VirtualPiece oldPiece = oldNode;
this.board.MovePiece(newPos, oldPos);
if (newPiece != null) {
this.board.InsertPiece(newPiece, newPos);
}
});
}
public void HandleBoardPosClick(Vector2 clickPos) {
if (board.ArrPosOutOfRange(clickPos)) return;
// GD.Print($"VirtualBoard {clickPos} clicked");
VirtualPiece clickChess = board.GetPiece(clickPos);
if (!selectedNode.HasSelected()) {
// Select piece
if (clickChess == null) {
// selectedNode.Clear();
return;
}
selectedNode.SetPos(clickPos);
} else if (clickChess == selectedNode.GetPiece()) {
// Unselect piece
selectedNode.Clear();
} else {
// Move piece
// GD.Print("default MoveFunc Move: ", selectedNode.GetPos(), "->", clickPos);
MoveAndRecord(clickPos, selectedNode.GetPos());
}
}
public void MoveAndRecord(Vector2 toPos, Vector2 fromPos) {
// GD.Print($"{fromPos} move to {toPos}");
VirtualPiece toChess = board.GetPiece(toPos);
VirtualPiece fromChess = board.GetPiece(fromPos);
fromChess?.Selected(false);
VirtualPiece NowNode;
if (toChess != null) {
NowNode = toChess;
board.RemovePiece(toPos);
} else {
NowNode = toChess;
}
moveRecords.AddRecord(NowNode, fromChess, toPos, fromPos);
OnMove?.Invoke(this, new VirtualBoard.MoveEventArgs { From = fromPos, To = toPos });
board.MovePiece(fromPos, toPos);
selectedNode.Clear();
}
public void Undo() {
// ChessPiece selected = selectedNode.GetPiece();
// selected?.DeSelected();
selectedNode.Clear();
moveRecords.Undo();
}
public void ReInit() {
moveRecords.Clear();
board.Clear();
selectedNode.Clear();
// board.InitChessBoard();
}
public void SetAllowedPieces(ArrayList allowedPieces) {
selectedNode.allowedPieces = allowedPieces;
}
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;
public SelectedPiece(VirtualBoard board) {
this.board = board;
}
public void Clear() {
if (selectedNodePos != Vector2.Inf) {
selectedNodePos = Vector2.Inf;
piece.Selected(false);
}
}
public void SetPos(Vector2 pos) {
// piece = board.GetNodeFromBoard(pos) as VirtualPiece;
piece = board.GetPiece(pos);
if (allowedPieces != null && allowedPieces.Contains(piece) == false) {
return;
}
selectedNodePos = pos;
piece.Selected(true);
}
public VirtualPiece GetPiece() {
return piece;
}
public Vector2 GetPos() {
return selectedNodePos;
}
public bool HasSelected() {
return selectedNodePos != Vector2.Inf;
}
}
}

View File

@ -1,33 +0,0 @@
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);
}

View File

@ -1,22 +0,0 @@
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);
}

View File

@ -1,133 +0,0 @@
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

@ -0,0 +1,99 @@
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

@ -0,0 +1,38 @@
using Vector2 = Godot.Vector2;
using System;
public class VirtualPiece {
private Vector2 pos; // 注意这个坐标的非像素坐标而是棋盘坐标
private 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

@ -0,0 +1,59 @@
// 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 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

@ -0,0 +1,10 @@
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
);
}

View File

@ -1,31 +0,0 @@
<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>

View File

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

View File

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