refactor(chess): 重构象棋程序基础结构,使用Nullable重构核心代码

- 更新了多个文件的代码结构和类型定义,提高了代码的健壮性和可维护性
- 优化了事件处理、棋子管理和移动逻辑,为后续功能扩展打下坚实基础
- 修复了一些潜在的空指针异常问题,提高了程序的稳定性
- 修复部分bug
This commit is contained in:
ZZY
2024-11-22 23:51:32 +08:00
parent 9c619784af
commit 327c2df94d
19 changed files with 307 additions and 117 deletions

View File

@ -6,20 +6,20 @@ using static IBoard;
public abstract class AbstractBoard : IBoard {
private readonly int rows;
private readonly int cols;
protected readonly IPiece[,] pieces;
protected readonly IPiece?[,] pieces;
protected readonly List<MoveRecord> moveRecords = new();
protected readonly int MAX_RECORDS;
public int Rows => rows;
public int Cols => cols;
public event EventHandler<SetPieceEventArgs> OnSetPiece;
public event EventHandler<IPiece> OnInsert;
public event EventHandler<IPiece> OnRemove;
public event EventHandler<MoveEventArgs> OnMove;
public event EventHandler<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 event EventHandler<MoveRecord>? OnAddRecord;
public event EventHandler<MoveRecord>? OnUndoRecord;
public AbstractBoard(int rows, int cols, int maxRecords = int.MaxValue) {
this.rows = rows;
@ -32,15 +32,15 @@ public abstract class AbstractBoard : IBoard {
return arrayPos.X < 0 || arrayPos.X >= Rows || arrayPos.Y < 0 || arrayPos.Y >= Cols;
}
public virtual IPiece GetPiece(Vector2I arrayPos) {
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) {
public virtual IPiece? SetPiece(IPiece? piece, Vector2I pos) {
if (IsPosOutOfRange(pos)) return null;
IPiece oldPiece = pieces[pos.X, pos.Y];
IPiece? oldPiece = pieces[pos.X, pos.Y];
pieces[pos.X, pos.Y] = piece;
if (piece != null) piece.Pos = pos;
// if (oldPiece != null) oldPiece.Pos = Vector2I.Zero;
@ -64,8 +64,8 @@ public abstract class AbstractBoard : IBoard {
return false;
}
IPiece piece = GetPiece(from);
if (GetPiece(to) != null && piece == null) {
IPiece? piece = GetPiece(from);
if (GetPiece(to) != null || piece == null) {
return false;
}
@ -75,8 +75,9 @@ public abstract class AbstractBoard : IBoard {
return true;
}
public virtual IPiece RemovePiece(Vector2I pos) {
IPiece piece = GetPiece(pos);
public virtual IPiece? RemovePiece(Vector2I pos) {
IPiece? piece = GetPiece(pos);
if (piece == null) return null;
OnRemove?.Invoke(this, piece);
return SetPiece(null, pos);
}
@ -96,7 +97,7 @@ public abstract class AbstractBoard : IBoard {
moveRecords.Clear();
}
public virtual void AddRecord(IPiece From, IPiece To, Vector2I FromPos, Vector2I ToPos) {
public virtual void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
if (moveRecords.Count >= MAX_RECORDS) {
moveRecords.RemoveAt(0);
}
@ -125,12 +126,12 @@ public abstract class AbstractBoard : IBoard {
}
public class MoveRecord {
public IPiece From { get; }
public IPiece To { get; }
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) {
public MoveRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
this.From = From;
this.To = To;
this.ToPos = ToPos;

View File

@ -46,10 +46,10 @@ public abstract class AbstractPiece : IPiece {
set => data = value;
}
public event EventHandler<Vector2I> OnPos;
public event EventHandler<Vector2I> OnMove;
public event EventHandler<bool> OnSelected;
public event EventHandler<string> OnName;
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)) {
@ -66,7 +66,7 @@ public abstract class AbstractPiece : IPiece {
return $"{Name} at {pos}";
}
public AbstractPiece(string name = "", Vector2I pos = null) {
public AbstractPiece(string name = "", Vector2I? pos = null) {
this.name = name;
this.pos = pos ?? new();
}

View File

@ -6,7 +6,7 @@ using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public class CCBoard : AbstractBoard
{
public event EventHandler<int> OnStepsChanged;
public event EventHandler<int>? OnStepsChanged;
private int steps = 0;
public int Steps {
get => steps;
@ -20,7 +20,7 @@ public class CCBoard : AbstractBoard
public CCBoard() : base(9, 10) {
}
public override void AddRecord(IPiece From, IPiece To, Vector2I FromPos, Vector2I ToPos) {
public override void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
base.AddRecord(From, To, FromPos, ToPos);
Steps += 1;
}

View File

@ -33,12 +33,19 @@ public class ChessCore {
playerSelf = new(board, Player.PlayerType.Human);
playerOpponent = new(board, Player.PlayerType.Human);
void func(object _self, IBoard.MoveEventArgs record) {
void func(object? _self, IBoard.MoveEventArgs record) {
// 防止 Undo 时 Selected Clear 出现 Null Pointer Exception
playerSelf.SelectedClear();
playerOpponent.SelectedClear();
board.AddRecord(board.GetPiece(record.From), board.GetPiece(record.To),
record.From, record.To);
// 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;

View File

@ -7,7 +7,7 @@ using Vector;
using static ChineseChess.ChessCore;
public class CCPiece : AbstractPiece {
public string CNName { get; protected set; }
public string CNName { get; protected set; } = "unknown";
public TurnsSideType TurnsSide { get; }
protected CCBoard board;
protected Vector2I localPos = new();
@ -16,14 +16,15 @@ public class CCPiece : AbstractPiece {
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;
public CCPiece(CCBoard? board = null, TurnsSideType turnsSide = TurnsSideType.Red,
string name = "", Vector2I? pos = null) : base(name) {
this.board = board ?? new();
TurnsSide = turnsSide;
OnPos += (sender, args) => {
localPos = Global2Local(Pos);
};
Pos = pos != null ? Local2Global(pos) : Vector2I.Zero;
Pos = pos is not null ? Local2Global(pos) : Vector2I.Zero;
localPos = Global2Local(Pos);
}
public override bool CanMove(Vector2I to) {
@ -38,8 +39,8 @@ public class CCPiece : AbstractPiece {
return TurnsSide == TurnsSideType.Red ? (transRedGlobal2Local * pos) : (transBlackGlobal2Local * pos);
}
protected CCPiece GetCCPieceLocal(in Vector2I pos) {
return (CCPiece)board.GetPiece(Local2Global(pos));
protected CCPiece? GetCCPieceLocal(in Vector2I pos) {
return (CCPiece?)board.GetPiece(Local2Global(pos));
}
public virtual List<Vector2I> CanMoveAllPosSelf() {
@ -49,7 +50,8 @@ public class CCPiece : AbstractPiece {
public IEnumerable<Vector2I> CanMoveAllPosLocal() {
var self = CanMoveAllPosSelf().Select(item => item + localPos);// 转换局部坐标
var ret = self.Where(item => {
bool ret = GetCCPieceLocal(item) == null || GetCCPieceLocal(item).TurnsSide != TurnsSide;
CCPiece? piece = GetCCPieceLocal(item);
bool ret = piece is null || piece.TurnsSide != TurnsSide;
// Console.WriteLine($"{item} can move: {ret}");
return ret;
}); // 过滤无效位置
@ -71,7 +73,7 @@ public class CCPiece : AbstractPiece {
return board.IsPosOutOfRange(Local2Global(pos));
}
protected CCPiece GetRecursivePieceLocal(Vector2I origin, Vector2I pos) {
protected CCPiece? GetRecursivePieceLocal(Vector2I origin, Vector2I pos) {
Vector2I with = origin + pos;
while (!IsPosOutOfRangeLocal(with) && GetCCPieceLocal(with) == null) {
with += pos;

View File

@ -7,7 +7,7 @@ public class Player {
private readonly CCBoard board;
private readonly SelectedPiece selectedNode;
public EventHandler<IBoard.MoveEventArgs> OnMove;
public EventHandler<IBoard.MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true;
public enum PlayerType {
@ -24,7 +24,7 @@ public class Player {
public void HandleBoardPosClick(Vector2I clickPos) {
if (board.IsPosOutOfRange(clickPos)) return;
// Console.WriteLine($"VirtualBoard {clickPos} clicked");
IPiece clickChess = board.GetPiece(clickPos);
IPiece? clickChess = board.GetPiece(clickPos);
if (!selectedNode.HasSelected()) {
// Select piece
@ -45,9 +45,10 @@ public class Player {
public void MoveAndRecord(Vector2I toPos, Vector2I fromPos) {
// GD.Print($"{fromPos} move to {toPos}");
IPiece toChess = board.GetPiece(toPos);
IPiece fromChess = board.GetPiece(fromPos);
IPiece? toChess = board.GetPiece(toPos);
IPiece? fromChess = board.GetPiece(fromPos);
if (fromChess != null) fromChess.IsSelected = false;
else return;
selectedNode.Clear();
if (!fromChess.CanMove(toPos)) {
@ -55,7 +56,8 @@ public class Player {
}
// MUST BE THERE !!! 防止删除节点后在启动回调导致错误
OnMove?.Invoke(this, new IBoard.MoveEventArgs { From = fromPos, To = toPos });
OnMove?.Invoke(this, new IBoard.MoveEventArgs
{ From = fromPos, To = toPos, Piece = fromChess });;
if (toChess != null) {
board.RemovePiece(toPos);
@ -76,9 +78,9 @@ public class Player {
private class SelectedPiece {
// Called when the node enters the scene tree for the first time.
private Vector2I selectedNodePos = Vector2I.MaxValue;
private IPiece piece;
private IPiece? piece = null;
private readonly CCBoard board;
public ArrayList allowedPieces = null;
public ArrayList? allowedPieces = null;
public SelectedPiece(CCBoard board) {
this.board = board;
@ -87,7 +89,8 @@ public class Player {
public void Clear() {
if (selectedNodePos != Vector2I.MaxValue) {
selectedNodePos = Vector2I.MaxValue;
piece.IsSelected = false;
if (piece != null)
piece.IsSelected = false;
}
// Console.WriteLine("SelectedPiece.Clear {0}", piece);
}
@ -98,11 +101,13 @@ public class Player {
return;
}
selectedNodePos = pos;
if (piece == null)
return;
piece.IsSelected = true;
Console.WriteLine("SelectedPiece.SetPos {0}", piece);
}
public IPiece GetPiece() {
public IPiece? GetPiece() {
return piece;
}

View File

@ -4,7 +4,7 @@ using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public class CCGeneral : CCPiece {
public CCGeneral(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCGeneral(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "General", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "帅";
@ -31,7 +31,7 @@ public class CCGeneral : CCPiece {
}
public class CCAdvisor : CCPiece {
public CCAdvisor(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCAdvisor(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Advisor", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "仕";
@ -58,7 +58,7 @@ public class CCAdvisor : CCPiece {
}
public class CCElephant : CCPiece {
public CCElephant(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCElephant(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Bishop", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "相";
@ -85,7 +85,7 @@ public class CCElephant : CCPiece {
}
public class CCHorse : CCPiece {
public CCHorse(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCHorse(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Horse", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "馬";
@ -125,7 +125,7 @@ public class CCHorse : CCPiece {
}
public class CCChariot : CCPiece {
public CCChariot(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCChariot(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Chariot", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "車";
@ -145,7 +145,7 @@ public class CCChariot : CCPiece {
break;
}
if (GetCCPieceLocal(ptr) != null) {
list.Add(ptr);
list.Add(ptr - localPos);
break;
}
list.Add(ptr - localPos);
@ -161,7 +161,7 @@ public class CCChariot : CCPiece {
}
public class CCCannon : CCPiece {
public CCCannon(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCCannon(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Cannon", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "砲";
@ -202,7 +202,7 @@ public class CCCannon : CCPiece {
}
public class CCPawn : CCPiece {
public CCPawn(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I pos = null)
public CCPawn(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Pawn", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "兵";

View File

@ -6,15 +6,15 @@ public interface IBoard {
public int Cols { get; }
public class SetPieceEventArgs : EventArgs {
public IPiece OldPiece { get; set; }
public IPiece NewPiece { get; set; }
public Vector2I Pos { get; set; }
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; }
public Vector2I To { get; set; }
public IPiece? Piece { get; set; }
public Vector2I From { get; set; } = new();
public Vector2I To { get; set; } = new();
}
event EventHandler<IPiece> OnInsert;
@ -23,10 +23,10 @@ public interface IBoard {
event EventHandler<SetPieceEventArgs> OnSetPiece;
bool IsPosOutOfRange(Vector2I pos);
IPiece GetPiece(Vector2I pos);
IPiece SetPiece(IPiece piece, 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);
IPiece? RemovePiece(Vector2I pos);
void Clear();
}

View File

@ -51,7 +51,7 @@ public class Vector2I {
return X * with.X + Y * with.Y;
}
public override bool Equals(object obj) {
public override bool Equals(object? obj) {
if (obj is Vector2I other) {
return this == other;
}