Compare commits

...

1 Commits
v0.0.4 ... main

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

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ bin/
*.sln
*.error
*.key*
.editorconfig
# cs project files
!*.csproj

View File

@ -1,8 +1,6 @@
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>

View File

@ -1,3 +1,4 @@
#nullable disable
using Godot;
using Godot.Collections;
using ChineseChess;
@ -39,15 +40,15 @@ 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);
// 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.OnStepsChanged += (sender, e) => {
board.OnStepsChanged += (sender, e) => {
turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black";
};

View File

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

View File

@ -4,12 +4,14 @@ 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 = new();
public Array<Vector2> canMovePos = [];
public override void _Ready() {
}
@ -19,29 +21,39 @@ public partial class ChessBoard : Node2D {
QueueRedraw();
}
public void LoadBoard(CCBoard board) {
board.OnRemove += (sender, piece) => {
if (piece?.Data.TryGetValue("Godot", out object node) ?? false) {
RemoveChild(node as Node);
}
};
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);
board.OnInsert += (sender, piece) => {
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);
};
// 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);
}
board.OnMove += (sender, vals) => {
from = PosTrans.transArrToPix * new Vector2(vals.From.X, vals.From.Y);
to = PosTrans.transArrToPix * new Vector2(vals.To.X, vals.To.Y);
QueueRedraw();
};
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() {

View File

@ -4,37 +4,31 @@ 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 IPiece piece;
public IPiece GetVirtualPiece() {
return piece;
}
private void OnPos(object _self, Vector.Vector2I oldPos) {
CCPiece self = (CCPiece)_self;
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);
}
public void OnSelected(object _self, bool oldIsSelected) {
CCPiece self = (CCPiece)_self;
ChessBoard chessBoard = GetParent() as ChessBoard;
void IPieceOn.OnSelected(object _self, bool oldIsSelected) {
if (GetParent() is not ChessBoard chessBoard || _self is not CCPiece self) return;
if (self.IsSelected) {
GD.Print($"{piece.Pos} is selected");
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));
@ -49,17 +43,15 @@ public partial class ChessPiece : Sprite2D {
new Vector2(0, 0)
);
public ChessPiece() : this(new CCPiece()){
public ChessPiece() : this(new CCPiece()) {
}
public ChessPiece(CCPiece piece) {
PieceLabel = piece.CNName;
this.piece = piece;
LabelColor = piece.TurnsSide == ChessCore.TurnsSideType.Red ? new Color("red") : new Color("black");
piece.OnPos += OnPos;
piece.OnSelected += OnSelected;
piece.On = this;
// Must Be Call for init Pos
OnPos(piece, piece.Pos);
piece.On.OnPos(piece, piece.Pos);
piece.Data.TryAdd("Godot", this);
}

View File

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

View File

@ -1,32 +1,20 @@
using System;
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
using static IBoard;
public abstract class AbstractBoard : IBoard {
private readonly int rows;
private readonly int cols;
protected readonly IPiece?[,] pieces;
protected readonly List<MoveRecord> moveRecords = new();
protected readonly int MAX_RECORDS;
public 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 event EventHandler<SetPieceEventArgs>? OnSetPiece;
public event EventHandler<IPiece>? OnInsert;
public event EventHandler<IPiece>? OnRemove;
public event EventHandler<MoveEventArgs>? OnMove;
public event EventHandler<MoveRecord>? OnAddRecord;
public event EventHandler<MoveRecord>? OnUndoRecord;
public AbstractBoard(int rows, int cols, int maxRecords = int.MaxValue) {
this.rows = rows;
this.cols = cols;
pieces = new IPiece[rows, cols];
MAX_RECORDS = maxRecords;
}
public virtual 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;
@ -42,19 +30,20 @@ public abstract class AbstractBoard : IBoard {
IPiece? oldPiece = pieces[pos.X, pos.Y];
pieces[pos.X, pos.Y] = piece;
if (piece != null) piece.Pos = pos;
if (piece is not null) piece.Pos = pos;
// if (oldPiece != null) oldPiece.Pos = Vector2I.Zero;
OnSetPiece?.Invoke(this, new SetPieceEventArgs { OldPiece = oldPiece, NewPiece = piece, Pos = pos });
on?.OnSetPieceInteral(this, new IBoardOn.SetPieceEventArgs
(oldPiece, piece, pos));
return oldPiece;
}
public virtual bool InsertPiece(IPiece piece, Vector2I pos) {
if (GetPiece(pos) != null && piece == null) {
if (GetPiece(pos) is not null && piece == null) {
return false;
}
OnInsert?.Invoke(this, piece);
on?.OnInsert(this, piece);
SetPiece(piece, pos);
return true;
}
@ -65,11 +54,11 @@ public abstract class AbstractBoard : IBoard {
}
IPiece? piece = GetPiece(from);
if (GetPiece(to) != null || piece == null) {
if (GetPiece(to) is not null || piece is null) {
return false;
}
OnMove?.Invoke(this, new MoveEventArgs { From = from, To = to, Piece = piece });
on?.OnMove(this, new IBoardOn.MoveEventArgs(piece, from, to));
SetPiece(null, from);
SetPiece(piece, to);
return true;
@ -77,8 +66,9 @@ public abstract class AbstractBoard : IBoard {
public virtual IPiece? RemovePiece(Vector2I pos) {
IPiece? piece = GetPiece(pos);
if (piece == null) return null;
OnRemove?.Invoke(this, piece);
if (piece is null) return null;
on?.OnRemove(this, piece);
return SetPiece(null, pos);
}
@ -103,7 +93,7 @@ public abstract class AbstractBoard : IBoard {
}
MoveRecord record = new(From, To, FromPos, ToPos);
OnAddRecord?.Invoke(this, record);
on?.OnAddRecord(this, record);
moveRecords.Add(record);
}
@ -113,29 +103,15 @@ public abstract class AbstractBoard : IBoard {
moveRecords.RemoveAt(moveRecords.Count - 1);
// 恢复新位置的棋子order is very inmportant
if (record.From != null) {
if (record.From is not null) {
MovePiece(record.ToPos, record.FromPos);
}
// 恢复旧位置的棋子
if (record.To != null) {
if (record.To is not null) {
InsertPiece(record.To, record.ToPos);
}
OnUndoRecord?.Invoke(this, record);
}
public class MoveRecord {
public IPiece? From { get; }
public IPiece? To { get; }
public Vector2I FromPos { get; }
public Vector2I ToPos { get; }
public MoveRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
this.From = From;
this.To = To;
this.ToPos = ToPos;
this.FromPos = FromPos;
}
on?.OnUndoRecord(this, record);
}
}

View File

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

View File

@ -1,24 +1,29 @@
using System;
using System.Collections;
using static ChineseChess.ChessCore;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public class CCBoard : AbstractBoard
{
public event EventHandler<int>? OnStepsChanged;
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;
OnStepsChanged?.Invoke(this, steps);
on?.OnSteps(this, oldSteps);
}
}
}
public CCBoard() : base(9, 10) {
}
public override void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
base.AddRecord(From, To, FromPos, ToPos);
@ -32,65 +37,49 @@ public class CCBoard : AbstractBoard
public (ArrayList, ArrayList) InitGame() {
Steps = 0;
// ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, new[] {
// new CCChariot(TurnsSideType.Black, "black chariot 0", 0, 0),
// ("车", 0, 0), ("马", 1, 0), ("象", 2, 0),
// ("士", 3, 0), ("将", 4, 0), ("士", 5, 0),
// ("象", 6, 0), ("马", 7, 0), ("车", 8, 0),
// ("炮", 1, 2), ("炮", 7, 2),
// ("卒", 0, 3), ("卒", 2, 3), ("卒", 4, 3), ("卒", 6, 3), ("卒", 8, 3)
// });
ArrayList 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[] {
// ("车", 0, -0), ("马", 1, -0), ("象", 2, -0),
// ("士", 3, -0), ("将", 4, -0), ("士", 5, -0),
// ("象", 6, -0), ("马", 7, -0), ("车", 8, -0),
// ("炮", 1, -2), ("炮", 7, -2),
// ("卒", 0, -3), ("卒", 2, -3), ("卒", 4, -3), ("卒", 6, -3), ("卒", 8, -3)
// });
ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, new CCPiece[] {
new CCChariot (this, TurnsSideType.Black, new Vector2I( 4, 0)), // 4,0
new CCHorse (this, TurnsSideType.Black, new Vector2I( 3, 0)), // 3,0
new CCElephant(this, TurnsSideType.Black, new Vector2I( 2, 0)), // 2,0
new CCAdvisor (this, TurnsSideType.Black, new Vector2I( 1, 0)), // 1,0
new CCGeneral (this, TurnsSideType.Black, new Vector2I( 0, 0)), // 0,0
new CCAdvisor (this, TurnsSideType.Black, new Vector2I(-1, 0)), // -1,0
new CCElephant(this, TurnsSideType.Black, new Vector2I(-2, 0)), // -2,0
new CCHorse (this, TurnsSideType.Black, new Vector2I(-3, 0)), // -3,0
new CCChariot (this, TurnsSideType.Black, new Vector2I(-4, 0)), // -4,0
new CCCannon (this, TurnsSideType.Black, new Vector2I( 3, 2)), // 3,2
new CCCannon (this, TurnsSideType.Black, new Vector2I(-3, 2)), // -3,2
new CCPawn (this, TurnsSideType.Black, new Vector2I(-4, 3)), // -4,3
new CCPawn (this, TurnsSideType.Black, new Vector2I(-2, 3)), // -2,3
new CCPawn (this, TurnsSideType.Black, new Vector2I( 0, 3)), // 0,3
new CCPawn (this, TurnsSideType.Black, new Vector2I( 2, 3)), // -4,3
new CCPawn (this, TurnsSideType.Black, new Vector2I( 4, 3)) // 4,3
});
ArrayList redPart = InitOnePartPieces(TurnsSideType.Red, new CCPiece[] {
new CCChariot (this, TurnsSideType.Red, new Vector2I( 4, 0)), // 4,0
new CCHorse (this, TurnsSideType.Red, new Vector2I( 3, 0)), // 3,0
new CCElephant(this, TurnsSideType.Red, new Vector2I( 2, 0)), // 2,0
new CCAdvisor (this, TurnsSideType.Red, new Vector2I( 1, 0)), // 1,0
new CCGeneral (this, TurnsSideType.Red, new Vector2I( 0, 0)), // 0,0
new CCAdvisor (this, TurnsSideType.Red, new Vector2I(-1, 0)), // -1,0
new CCElephant(this, TurnsSideType.Red, new Vector2I(-2, 0)), // -2,0
new CCHorse (this, TurnsSideType.Red, new Vector2I(-3, 0)), // -3,0
new CCChariot (this, TurnsSideType.Red, new Vector2I(-4, 0)), // -4,0
new CCCannon (this, TurnsSideType.Red, new Vector2I( 3, 2)), // 3,2
new CCCannon (this, TurnsSideType.Red, new Vector2I(-3, 2)), // -3,2
new CCPawn (this, TurnsSideType.Red, new Vector2I(-4, 3)), // -4,3
new CCPawn (this, TurnsSideType.Red, new Vector2I(-2, 3)), // -2,3
new CCPawn (this, TurnsSideType.Red, new Vector2I( 0, 3)), // 0,3
new CCPawn (this, TurnsSideType.Red, new Vector2I( 2, 3)), // 2,3
new CCPawn (this, TurnsSideType.Red, new Vector2I( 4, 3)) // 4,3
});
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 = new();
ArrayList list = [];
foreach (var piece in pieces) {
list.Add(piece);
InsertPiece(piece, piece.Pos);

View File

@ -1,6 +1,8 @@
using Vector2 = Vector.Vector2I;
using System;
using System.Collections;
using static IBoard.IBoardOn;
namespace ChineseChess;
public class ChessCore {
@ -23,17 +25,18 @@ public class ChessCore {
private TurnsSideType sideType = TurnsSideType.Red;
private readonly TurnsSideType selfSide;
public readonly CCBoard board = new();
public readonly CCBoard board;
private readonly Player playerSelf;
private readonly Player playerOpponent;
// public EventHandler<IBoard.MoveEventArgs> OnMove;
public ChessCore(Mode mode, TurnsSideType selfSide) {
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, IBoard.MoveEventArgs record) {
void func(object? _self, MoveEventArgs record) {
// 防止 Undo 时 Selected Clear 出现 Null Pointer Exception
playerSelf.SelectedClear();
playerOpponent.SelectedClear();

View File

@ -1,6 +1,5 @@
namespace ChineseChess;
using System;
using System.Collections.Generic;
using System.Linq;
using Vector;
@ -20,11 +19,14 @@ public class CCPiece : AbstractPiece {
string name = "", Vector2I? pos = null) : base(name) {
this.board = board ?? new();
TurnsSide = turnsSide;
OnPos += (sender, args) => {
localPos = Global2Local(Pos);
};
Pos = pos is not null ? Local2Global(pos) : Vector2I.Zero;
localPos = Global2Local(Pos);
}
public override Vector2I Pos {
get => base.Pos; set {
base.Pos = value;
localPos = Global2Local(Pos);
}
}
public override bool CanMove(Vector2I to) {
@ -44,18 +46,18 @@ public class CCPiece : AbstractPiece {
}
public virtual List<Vector2I> CanMoveAllPosSelf() {
return new List<Vector2I>();
return [];
}
public IEnumerable<Vector2I> CanMoveAllPosLocal() {
var self = CanMoveAllPosSelf().Select(item => item + localPos);// 转换局部坐标
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));
@ -64,7 +66,7 @@ public class CCPiece : AbstractPiece {
public IEnumerable<Vector2I> CanMoveAllPos() {
var ret = CanMoveAllPosLocal()
.Select(item => Local2Global(item)); // 转换为全局坐标
.Select(item => Local2Global(item));
// Console.WriteLine("CanMoveAllPos: {0}", string.Join(",", ret));
return ret;
}

View File

@ -3,11 +3,12 @@ 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<IBoard.MoveEventArgs>? OnMove;
public EventHandler<MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true;
public enum PlayerType {
@ -15,8 +16,7 @@ public class Player {
AI
}
public Player(CCBoard board, PlayerType type = PlayerType.Human)
{
public Player(CCBoard board, PlayerType type = PlayerType.Human) {
this.board = board;
this.selectedNode = new SelectedPiece(board);
}
@ -56,9 +56,9 @@ public class Player {
}
// MUST BE THERE !!! 防止删除节点后在启动回调导致错误
OnMove?.Invoke(this, new IBoard.MoveEventArgs
{ From = fromPos, To = toPos, Piece = fromChess });;
OnMove?.Invoke(this, new MoveEventArgs
(fromChess, fromPos, toPos));
if (toChess != null) {
board.RemovePiece(toPos);
}

View File

@ -1,32 +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; }
public class SetPieceEventArgs : EventArgs {
public IPiece? OldPiece { get; set; }
public IPiece? NewPiece { get; set; }
public Vector2I Pos { get; set; } = new();
}
public class MoveEventArgs : EventArgs {
public IPiece? Piece { get; set; }
public Vector2I From { get; set; } = new();
public Vector2I To { get; set; } = new();
}
event EventHandler<IPiece> OnInsert;
event EventHandler<IPiece> OnRemove;
event EventHandler<MoveEventArgs> OnMove;
event EventHandler<SetPieceEventArgs> OnSetPiece;
bool IsPosOutOfRange(Vector2I pos);
IPiece? GetPiece(Vector2I pos);
IPiece? SetPiece(IPiece piece, Vector2I pos);
IPiece? SetPiece(IPiece? piece, Vector2I pos);
bool InsertPiece(IPiece piece, Vector2I pos);
bool MovePiece(Vector2I from, Vector2I to);
IPiece? RemovePiece(Vector2I pos);
void Clear();
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

@ -2,17 +2,21 @@ 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; }
event EventHandler<Vector2I> OnPos;
event EventHandler<Vector2I> OnMove;
event EventHandler<bool> OnSelected;
event EventHandler<string> OnName;
bool CanMove(Vector2I to);
bool Move(Vector2I pos);
}

View File

@ -21,11 +21,11 @@
<Using Include="NUnit.Framework" />
</ItemGroup>
<!-- <ItemGroup>
<ProjectReference Include="..\Chinese_Chess.csproj" />
</ItemGroup> -->
<ItemGroup>
<Compile Include="..\Scripts\Src\*.cs" />
<ProjectReference Include="..\*.csproj" />
</ItemGroup>
<!-- <ItemGroup>
<Compile Include="..\Scripts\Src\*.cs" />
</ItemGroup> -->
</Project>

View File

@ -1,21 +1,147 @@
using Vector;
namespace Test;
public class TestsVector
{
namespace Test.Vector;
public class TestsVector2I {
[SetUp]
public void Setup()
{
public void Setup() {
}
[Test]
public void Test1()
{
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()
{
public void TestVector2I_Constructors() {
// Arrange & Act
var v1 = new Vector2I();
var v2 = new Vector2I(1, 2);
@ -31,8 +157,7 @@ public class TestsVector
}
[Test]
public void TestVector2I_Operators()
{
public void TestVector2I_Operators() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
@ -49,8 +174,7 @@ public class TestsVector
}
[Test]
public void TestTrans2DI_Constructors()
{
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));
@ -76,8 +200,7 @@ public class TestsVector
}
[Test]
public void TestTrans2DI_Operators()
{
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);
@ -90,4 +213,51 @@ public class TestsVector
Assert.That(v2, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v3, Is.EqualTo(new Vector2I(2, 2)));
}
[Test]
public void TestTrans2DI_BoundaryValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(int.MaxValue, 0), new Vector2I(0, int.MaxValue), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(int.MinValue, 0), new Vector2I(0, int.MinValue), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(int.MaxValue, int.MaxValue)));
Assert.That(v3, Is.EqualTo(new Vector2I(int.MinValue, int.MinValue)));
}
[Test]
public void TestTrans2DI_SpecialValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(0, 0), new Vector2I(0, 0), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(v3, Is.EqualTo(new Vector2I(1, 1)));
}
[Test]
public void TestTrans2DI_Multiplication() {
// Arrange
var t1 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(1, 2));
var t2 = new Trans2DI(new Vector2I(2, 0), new Vector2I(0, 2), new Vector2I(3, 4));
// Act
var t3 = t1 * t2;
// Assert
Assert.That(t3.XAxis, Is.EqualTo(new Vector2I(2, 0)));
Assert.That(t3.YAxis, Is.EqualTo(new Vector2I(0, 2)));
Assert.That(t3.Origin, Is.EqualTo(new Vector2I(5, 8)));
}
}

View File

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