From 7ed771d467d71c56a4e7d1beb4935c0e870f4d6a Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Thu, 4 Mar 2021 20:35:23 -0600 Subject: [PATCH] Code smells --- Benchmarking/Benchmarks.cs | 2 +- Gameboard.ShogiUI.BoardState/Direction.cs | 18 ---- Gameboard.ShogiUI.BoardState/Extensions.cs | 19 ---- .../Gameboard.ShogiUI.BoardState.csproj | 1 + Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs | 40 +++----- .../Pieces/GoldGeneral.cs | 22 ++--- Gameboard.ShogiUI.BoardState/Pieces/King.cs | 24 +++-- Gameboard.ShogiUI.BoardState/Pieces/Knight.cs | 14 +-- Gameboard.ShogiUI.BoardState/Pieces/Lance.cs | 14 +-- Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs | 14 +-- .../{ => Pieces}/Piece.cs | 36 ++----- Gameboard.ShogiUI.BoardState/Pieces/Rook.cs | 37 +++---- .../Pieces/SilverGeneral.cs | 22 ++--- .../{Array2D.cs => PlanarCollection.cs} | 30 +----- Gameboard.ShogiUI.BoardState/Position.cs | 35 ------- Gameboard.ShogiUI.BoardState/ShogiBoard.cs | 40 +------- ...board.ShogiUI.Sockets.ServiceModels.csproj | 1 + .../Gameboard.ShogiUI.Sockets.csproj | 1 + Gameboard.ShogiUI.Sockets/Models/Move.cs | 2 +- .../BoardState/BoardStateExtensions.cs | 65 ++++++++++++ .../BoardState/ShogiBoardShould.cs | 98 +++++++++++++++++-- .../PathFinding/PathFinder2DShould.cs | 16 +-- PathFinding/Direction.cs | 18 ++++ PathFinding/Distance.cs | 8 ++ PathFinding/Enums.cs | 19 ---- PathFinding/IPlanarCollection.cs | 2 +- PathFinding/IPlanarElement.cs | 5 +- PathFinding/{Path.cs => Move.cs} | 4 +- PathFinding/MoveSet.cs | 23 +++++ PathFinding/PathFinder2D.cs | 18 ++-- PathFinding/PathFinding.csproj | 1 + 31 files changed, 310 insertions(+), 339 deletions(-) delete mode 100644 Gameboard.ShogiUI.BoardState/Direction.cs delete mode 100644 Gameboard.ShogiUI.BoardState/Extensions.cs rename Gameboard.ShogiUI.BoardState/{ => Pieces}/Piece.cs (55%) rename Gameboard.ShogiUI.BoardState/{Array2D.cs => PlanarCollection.cs} (64%) delete mode 100644 Gameboard.ShogiUI.BoardState/Position.cs create mode 100644 Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs create mode 100644 PathFinding/Direction.cs create mode 100644 PathFinding/Distance.cs delete mode 100644 PathFinding/Enums.cs rename PathFinding/{Path.cs => Move.cs} (77%) create mode 100644 PathFinding/MoveSet.cs diff --git a/Benchmarking/Benchmarks.cs b/Benchmarking/Benchmarks.cs index a972747..9969cb8 100644 --- a/Benchmarking/Benchmarks.cs +++ b/Benchmarking/Benchmarks.cs @@ -12,7 +12,7 @@ namespace Benchmarking { private readonly Move[] moves; private readonly Vector2[] directions; - private readonly Consumer consumer = new Consumer(); + private readonly Consumer consumer = new(); public Benchmarks() { diff --git a/Gameboard.ShogiUI.BoardState/Direction.cs b/Gameboard.ShogiUI.BoardState/Direction.cs deleted file mode 100644 index d474f79..0000000 --- a/Gameboard.ShogiUI.BoardState/Direction.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Numerics; - -namespace Gameboard.ShogiUI.BoardState -{ - public static class Direction - { - public static readonly Vector2 Up = new Vector2(0, 1); - public static readonly Vector2 Down = new Vector2(0, -1); - public static readonly Vector2 Left = new Vector2(-1, 0); - public static readonly Vector2 Right = new Vector2(1, 0); - public static readonly Vector2 UpLeft = new Vector2(-1, 1); - public static readonly Vector2 UpRight = new Vector2(1, 1); - public static readonly Vector2 DownLeft = new Vector2(-1, -1); - public static readonly Vector2 DownRight = new Vector2(1, -1); - public static readonly Vector2 KnightLeft = new Vector2(-1, 2); - public static readonly Vector2 KnightRight = new Vector2(1, 2); - } -} diff --git a/Gameboard.ShogiUI.BoardState/Extensions.cs b/Gameboard.ShogiUI.BoardState/Extensions.cs deleted file mode 100644 index 19dd810..0000000 --- a/Gameboard.ShogiUI.BoardState/Extensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -namespace Gameboard.ShogiUI.BoardState -{ - public static class Extensions - { - public static void ForEachNotNull(this Piece[,] array, Action action) - { - for (var x = 0; x < array.GetLength(0); x++) - for (var y = 0; y < array.GetLength(1); y++) - { - var piece = array[x, y]; - if (piece != null) - { - action(piece, x, y); - } - } - } - } -} diff --git a/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj b/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj index 23d6a53..1a2ca2c 100644 --- a/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj +++ b/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj @@ -3,6 +3,7 @@ net5.0 true + 5 diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs b/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs index cf73954..f91e6ea 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs @@ -1,34 +1,32 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class Bishop : Piece { - private static readonly List MoveSet = new List(4) + private static readonly List Moves = new(4) { - new Path(Direction.UpLeft, Distance.MultiStep), - new Path(Direction.UpRight, Distance.MultiStep), - new Path(Direction.DownLeft, Distance.MultiStep), - new Path(Direction.DownRight, Distance.MultiStep) + new PathFinding.Move(Direction.UpLeft, Distance.MultiStep), + new PathFinding.Move(Direction.UpRight, Distance.MultiStep), + new PathFinding.Move(Direction.DownLeft, Distance.MultiStep), + new PathFinding.Move(Direction.DownRight, Distance.MultiStep) }; - private static readonly List PromotedMoveSet = new List(8) + private static readonly List PromotedMoves = new(8) { - new Path(Direction.Up), - new Path(Direction.Left), - new Path(Direction.Right), - new Path(Direction.Down), - new Path(Direction.UpLeft, Distance.MultiStep), - new Path(Direction.UpRight, Distance.MultiStep), - new Path(Direction.DownLeft, Distance.MultiStep), - new Path(Direction.DownRight, Distance.MultiStep) + new PathFinding.Move(Direction.Up), + new PathFinding.Move(Direction.Left), + new PathFinding.Move(Direction.Right), + new PathFinding.Move(Direction.Down), + new PathFinding.Move(Direction.UpLeft, Distance.MultiStep), + new PathFinding.Move(Direction.UpRight, Distance.MultiStep), + new PathFinding.Move(Direction.DownLeft, Distance.MultiStep), + new PathFinding.Move(Direction.DownRight, Distance.MultiStep) }; public Bishop(WhichPlayer owner) : base(WhichPiece.Bishop, owner) { - // TODO: If this strat works out, we can do away with the Direction class entirely. - PromotedMoveSet.AddRange(MoveSet); + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, PromotedMoves); } public override Piece DeepClone() @@ -37,11 +35,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? PromotedMoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs b/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs index cd22d62..121903d 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs @@ -1,23 +1,23 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class GoldenGeneral : Piece { - public static readonly List MoveSet = new List(6) + public static readonly List Moves = new(6) { - new Path(Direction.Up), - new Path(Direction.UpLeft), - new Path(Direction.UpRight), - new Path(Direction.Left), - new Path(Direction.Right), - new Path(Direction.Down) + new PathFinding.Move(Direction.Up), + new PathFinding.Move(Direction.UpLeft), + new PathFinding.Move(Direction.UpRight), + new PathFinding.Move(Direction.Left), + new PathFinding.Move(Direction.Right), + new PathFinding.Move(Direction.Down) }; public GoldenGeneral(WhichPlayer owner) : base(WhichPiece.GoldenGeneral, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, Moves); } public override Piece DeepClone() @@ -26,9 +26,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() => Owner == WhichPlayer.Player1 - ? MoveSet - : MoveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/King.cs b/Gameboard.ShogiUI.BoardState/Pieces/King.cs index e2d4ebd..b799a06 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/King.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/King.cs @@ -1,25 +1,25 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class King : Piece { - private static readonly List MoveSet = new List(8) + private static readonly List Moves = new(8) { - new Path(Direction.Up), - new Path(Direction.Left), - new Path(Direction.Right), - new Path(Direction.Down), - new Path(Direction.UpLeft), - new Path(Direction.UpRight), - new Path(Direction.DownLeft), - new Path(Direction.DownRight) + new PathFinding.Move(Direction.Up), + new PathFinding.Move(Direction.Left), + new PathFinding.Move(Direction.Right), + new PathFinding.Move(Direction.Down), + new PathFinding.Move(Direction.UpLeft), + new PathFinding.Move(Direction.UpRight), + new PathFinding.Move(Direction.DownLeft), + new PathFinding.Move(Direction.DownRight) }; public King(WhichPlayer owner) : base(WhichPiece.King, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, Moves); } public override Piece DeepClone() @@ -28,7 +28,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - // The move set for a King is the same regardless of orientation. - public override ICollection GetPaths() => MoveSet; } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs b/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs index 7e4a4df..efab3aa 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs @@ -7,14 +7,16 @@ namespace Gameboard.ShogiUI.BoardState.Pieces { public class Knight : Piece { - private static readonly List MoveSet = new List(2) + private static readonly List Moves = new(2) { - new Path(Direction.KnightLeft), - new Path(Direction.KnightRight) + new PathFinding.Move(Direction.KnightLeft), + new PathFinding.Move(Direction.KnightRight) }; public Knight(WhichPlayer owner) : base(WhichPiece.Knight, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves); } public override Piece DeepClone() @@ -23,11 +25,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs b/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs index a9f2df7..0329ee1 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs @@ -1,19 +1,19 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class Lance : Piece { - private static readonly List MoveSet = new List(1) + private static readonly List Moves = new(1) { - new Path(Direction.Up, Distance.MultiStep), + new PathFinding.Move(Direction.Up, Distance.MultiStep), }; public Lance(WhichPlayer owner) : base(WhichPiece.Lance, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves); } public override Piece DeepClone() @@ -22,11 +22,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs b/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs index bce9f14..4710bc3 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs @@ -1,19 +1,19 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class Pawn : Piece { - private static readonly List MoveSet = new List(1) + private static readonly List Moves = new(1) { - new Path(Direction.Up) + new PathFinding.Move(Direction.Up) }; public Pawn(WhichPlayer owner) : base(WhichPiece.Pawn, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves); } public override Piece DeepClone() @@ -22,11 +22,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Piece.cs b/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs similarity index 55% rename from Gameboard.ShogiUI.BoardState/Piece.cs rename to Gameboard.ShogiUI.BoardState/Pieces/Piece.cs index 99bca18..071d301 100644 --- a/Gameboard.ShogiUI.BoardState/Piece.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs @@ -1,15 +1,20 @@ using PathFinding; -using System.Collections.Generic; using System.Diagnostics; -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.BoardState.Pieces { [DebuggerDisplay("{WhichPiece} {Owner}")] public abstract class Piece : IPlanarElement { + protected MoveSet promotedMoveSet; + protected MoveSet moveSet; + + public MoveSet MoveSet => IsPromoted ? promotedMoveSet : moveSet; + public abstract Piece DeepClone(); public WhichPiece WhichPiece { get; } public WhichPlayer Owner { get; private set; } public bool IsPromoted { get; private set; } + public bool IsUpsideDown => Owner == WhichPlayer.Player2; public Piece(WhichPiece piece, WhichPlayer owner) { @@ -22,27 +27,6 @@ namespace Gameboard.ShogiUI.BoardState && WhichPiece != WhichPiece.King && WhichPiece != WhichPiece.GoldenGeneral; - public string ShortName => WhichPiece switch - { - WhichPiece.King => " K ", - WhichPiece.GoldenGeneral => " G ", - WhichPiece.SilverGeneral => IsPromoted ? "^S^" : " S ", - WhichPiece.Bishop => IsPromoted ? "^B^" : " B ", - WhichPiece.Rook => IsPromoted ? "^R^" : " R ", - WhichPiece.Knight => IsPromoted ? "^k^" : " k ", - WhichPiece.Lance => IsPromoted ? "^L^" : " L ", - WhichPiece.Pawn => IsPromoted ? "^P^" : " P ", - _ => " ? ", - }; - - public bool IsRanged => WhichPiece switch - { - WhichPiece.Bishop => true, - WhichPiece.Rook => true, - WhichPiece.Lance => !IsPromoted, - _ => false, - }; - public void ToggleOwnership() { Owner = Owner == WhichPlayer.Player1 @@ -59,11 +43,5 @@ namespace Gameboard.ShogiUI.BoardState ToggleOwnership(); Demote(); } - - public abstract ICollection GetPaths(); - - public abstract Piece DeepClone(); - - public bool IsUpsideDown => Owner == WhichPlayer.Player2; } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs b/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs index dfedd94..709c82f 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs @@ -1,32 +1,32 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class Rook : Piece { - private static readonly List MoveSet = new List(4) + private static readonly List Moves = new(4) { - new Path(Direction.Up, Distance.MultiStep), - new Path(Direction.Left, Distance.MultiStep), - new Path(Direction.Right, Distance.MultiStep), - new Path(Direction.Down, Distance.MultiStep) + new PathFinding.Move(Direction.Up, Distance.MultiStep), + new PathFinding.Move(Direction.Left, Distance.MultiStep), + new PathFinding.Move(Direction.Right, Distance.MultiStep), + new PathFinding.Move(Direction.Down, Distance.MultiStep) }; - private static readonly List PromotedMoveSet = new List(8) + private static readonly List PromotedMoves = new(8) { - new Path(Direction.Up, Distance.MultiStep), - new Path(Direction.Left, Distance.MultiStep), - new Path(Direction.Right, Distance.MultiStep), - new Path(Direction.Down, Distance.MultiStep), - new Path(Direction.UpLeft), - new Path(Direction.UpRight), - new Path(Direction.DownLeft), - new Path(Direction.DownRight) + new PathFinding.Move(Direction.Up, Distance.MultiStep), + new PathFinding.Move(Direction.Left, Distance.MultiStep), + new PathFinding.Move(Direction.Right, Distance.MultiStep), + new PathFinding.Move(Direction.Down, Distance.MultiStep), + new PathFinding.Move(Direction.UpLeft), + new PathFinding.Move(Direction.UpRight), + new PathFinding.Move(Direction.DownLeft), + new PathFinding.Move(Direction.DownRight) }; public Rook(WhichPlayer owner) : base(WhichPiece.Rook, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, PromotedMoves); } public override Piece DeepClone() @@ -35,10 +35,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? PromotedMoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs b/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs index ef0bcbd..557c3b0 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs @@ -1,22 +1,22 @@ using PathFinding; using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState.Pieces { public class SilverGeneral : Piece { - private static readonly List MoveSet = new List(4) + private static readonly List Moves = new(4) { - new Path(Direction.Up), - new Path(Direction.UpLeft), - new Path(Direction.UpRight), - new Path(Direction.DownLeft), - new Path(Direction.DownRight) + new PathFinding.Move(Direction.Up), + new PathFinding.Move(Direction.UpLeft), + new PathFinding.Move(Direction.UpRight), + new PathFinding.Move(Direction.DownLeft), + new PathFinding.Move(Direction.DownRight) }; public SilverGeneral(WhichPlayer owner) : base(WhichPiece.SilverGeneral, owner) { + moveSet = new MoveSet(this, Moves); + promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves); } public override Piece DeepClone() @@ -25,11 +25,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - - public override ICollection GetPaths() - { - var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet; - return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } } } diff --git a/Gameboard.ShogiUI.BoardState/Array2D.cs b/Gameboard.ShogiUI.BoardState/PlanarCollection.cs similarity index 64% rename from Gameboard.ShogiUI.BoardState/Array2D.cs rename to Gameboard.ShogiUI.BoardState/PlanarCollection.cs index 6487646..f093035 100644 --- a/Gameboard.ShogiUI.BoardState/Array2D.cs +++ b/Gameboard.ShogiUI.BoardState/PlanarCollection.cs @@ -2,19 +2,17 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Numerics; namespace Gameboard.ShogiUI.BoardState { - public class Array2D : IPlanarCollection, IEnumerable + public class PlanarCollection : IPlanarCollection, IEnumerable where T : IPlanarElement { - /// False to stop iterating. public delegate void ForEachDelegate(T element, int x, int y); private readonly T[] array; private readonly int width; private readonly int height; - public Array2D(int width, int height) + public PlanarCollection(int width, int height) { this.width = width; this.height = height; @@ -39,17 +37,6 @@ namespace Gameboard.ShogiUI.BoardState _ => throw new IndexOutOfRangeException() }; - public void ForEach(ForEachDelegate callback) - { - for (var x = 0; x < width; x++) - { - for (var y = 0; y < height; y++) - { - callback(this[x, y], x, y); - } - } - } - public void ForEachNotNull(ForEachDelegate callback) { for (var x = 0; x < width; x++) @@ -62,19 +49,6 @@ namespace Gameboard.ShogiUI.BoardState } } - public Vector2? IndexOf(Predicate predicate) - { - for (var x = 0; x < width; x++) - for (var y = 0; y < height; y++) - { - if (this[x, y] != null && predicate(this[x, y])) - { - return new Vector2(x, y); - } - } - return null; - } - public IEnumerator GetEnumerator() { foreach (var item in array) yield return item; diff --git a/Gameboard.ShogiUI.BoardState/Position.cs b/Gameboard.ShogiUI.BoardState/Position.cs deleted file mode 100644 index 71552d7..0000000 --- a/Gameboard.ShogiUI.BoardState/Position.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Gameboard.ShogiUI.BoardState -{ - public class Position - { - private int x; - private int y; - - public int X - { - get => x; - set { - if (value > 8 || value < 0) throw new ArgumentOutOfRangeException(); - x = value; - } - } - - public int Y - { - get => y; - set - { - if (value > 8 || value < 0) throw new ArgumentOutOfRangeException(); - y = value; - } - } - - public Position(int x, int y) - { - X = x; - Y = y; - } - } -} diff --git a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs index bb1cb62..6242196 100644 --- a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs +++ b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs @@ -20,7 +20,7 @@ namespace Gameboard.ShogiUI.BoardState private Vector2 player1King; private Vector2 player2King; public IReadOnlyDictionary> Hands { get; } - public Array2D Board { get; } + public PlanarCollection Board { get; } public List MoveHistory { get; } public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2; public WhichPlayer? InCheck { get; private set; } @@ -28,7 +28,7 @@ namespace Gameboard.ShogiUI.BoardState public ShogiBoard() { - Board = new Array2D(9, 9); + Board = new PlanarCollection(9, 9); MoveHistory = new List(20); Hands = new Dictionary> { { WhichPlayer.Player1, new List()}, @@ -54,7 +54,7 @@ namespace Gameboard.ShogiUI.BoardState private ShogiBoard(ShogiBoard toCopy) { - Board = new Array2D(9, 9); + Board = new PlanarCollection(9, 9); for (var x = 0; x < 9; x++) for (var y = 0; y < 9; y++) Board[x, y] = toCopy.Board[x, y]?.DeepClone(); @@ -213,40 +213,6 @@ namespace Gameboard.ShogiUI.BoardState return !isObstructed && isPathable; } - public void PrintStateAsAscii() - { - var builder = new StringBuilder(); - builder.Append(" Player 2"); - builder.AppendLine(); - for (var y = 8; y > -1; y--) - { - builder.Append("- "); - for (var x = 0; x < 8; x++) builder.Append("- - "); - builder.Append("- -"); - builder.AppendLine(); - builder.Append('|'); - for (var x = 0; x < 9; x++) - { - var piece = Board[x, y]; - if (piece == null) - { - builder.Append(" "); - } - else - { - builder.AppendFormat("{0}", piece.ShortName); - } - builder.Append('|'); - } - builder.AppendLine(); - } - builder.Append("- "); - for (var x = 0; x < 8; x++) builder.Append("- - "); - builder.Append("- -"); - builder.AppendLine(); - builder.Append(" Player 1"); - Console.WriteLine(builder.ToString()); - } #region Rules Validation private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer) { diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj b/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj index 423f013..ee29921 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj @@ -3,6 +3,7 @@ net5.0 true + 5 diff --git a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj index 053dcd9..3b3a6f8 100644 --- a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj +++ b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj @@ -3,6 +3,7 @@ net5.0 true + 5 diff --git a/Gameboard.ShogiUI.Sockets/Models/Move.cs b/Gameboard.ShogiUI.Sockets/Models/Move.cs index b61d8d7..66fda38 100644 --- a/Gameboard.ShogiUI.Sockets/Models/Move.cs +++ b/Gameboard.ShogiUI.Sockets/Models/Move.cs @@ -44,7 +44,7 @@ namespace Gameboard.ShogiUI.Sockets.Models IsPromotion = move.IsPromotion; PieceFromCaptured = pieceFromCaptured; } - public ServiceModels.Socket.Types.Move ToServiceModel() => new ServiceModels.Socket.Types.Move + public ServiceModels.Socket.Types.Move ToServiceModel() => new() { From = From.ToBoardNotation(), IsPromotion = IsPromotion, diff --git a/Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs b/Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs new file mode 100644 index 0000000..9eac527 --- /dev/null +++ b/Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs @@ -0,0 +1,65 @@ +using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.BoardState.Pieces; +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Gameboard.ShogiUI.UnitTests.BoardState +{ + public static class BoardStateExtensions + { + public static string GetShortName(this Piece self) + { + var name = self.WhichPiece switch + { + WhichPiece.King => " K ", + WhichPiece.GoldenGeneral => " G ", + WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ", + WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ", + WhichPiece.Rook => self.IsPromoted ? "^R " : " R ", + WhichPiece.Knight => self.IsPromoted ? "^k " : " k ", + WhichPiece.Lance => self.IsPromoted ? "^L " : " L ", + WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ", + _ => " ? ", + }; + if (self.Owner == WhichPlayer.Player2) + name = Regex.Replace(name, @"([^\s]+)\s", "$1."); + return name; + } + + public static void PrintStateAsAscii(this ShogiBoard self) + { + var builder = new StringBuilder(); + builder.Append(" Player 2(.)"); + builder.AppendLine(); + for (var y = 8; y > -1; y--) + { + builder.Append("- "); + for (var x = 0; x < 8; x++) builder.Append("- - "); + builder.Append("- -"); + builder.AppendLine(); + builder.Append('|'); + for (var x = 0; x < 9; x++) + { + var piece = self.Board[x, y]; + if (piece == null) + { + builder.Append(" "); + } + else + { + builder.AppendFormat("{0}", piece.GetShortName()); + } + builder.Append('|'); + } + builder.AppendLine(); + } + builder.Append("- "); + for (var x = 0; x < 8; x++) builder.Append("- - "); + builder.Append("- -"); + builder.AppendLine(); + builder.Append(" Player 1"); + Console.WriteLine(builder.ToString()); + } + } +} diff --git a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs index e466a58..1178c55 100644 --- a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs +++ b/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.BoardState.Pieces; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; @@ -81,7 +82,6 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState shogi.Board[0, 3].WhichPiece.Should().Be(WhichPiece.Pawn); } - [TestMethod] public void PreventInvalidMoves_MoveFromEmptyPosition() { @@ -245,30 +245,110 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var shogi = new ShogiBoard(moves); shogi.PrintStateAsAscii(); - // Prerequisite + // Prerequisites shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); // Act | Assert - It is P1 turn - // try illegally placing Knight from the hand. + /// try illegally placing Knight from the hand. shogi.Board[7, 8].Should().BeNull(); - var moveSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 8) }); - shogi.PrintStateAsAscii(); - moveSuccess.Should().BeFalse(); + var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 8) }); + dropSuccess.Should().BeFalse(); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); shogi.Board[7, 8].Should().BeNull(); + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 7) }); + dropSuccess.Should().BeFalse(); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + shogi.Board[7, 7].Should().BeNull(); - // Assert - //var pawnDropSuccess = shogi.Move(new Move) + /// try illegally placing Pawn from the hand + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Pawn, To = new Vector2(7, 8) }); + dropSuccess.Should().BeFalse(); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + shogi.Board[7, 8].Should().BeNull(); - // Assert + /// try illegally placing Lance from the hand + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(7, 8) }); + dropSuccess.Should().BeFalse(); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + shogi.Board[7, 8].Should().BeNull(); } [TestMethod] public void PreventInvalidDrop_Check() { + // Arrange + var moves = new[] + { + // P1 Pawn + new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + // P2 Pawn + new Move { From = new Vector2(8, 6), To = new Vector2(8, 5) }, + // P1 Bishop, check + new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }, + // P2 Gold, block check + new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) }, + // P1 arbitrary move + new Move { From = new Vector2(0, 2), To = new Vector2(0, 3) }, + // P2 Bishop + new Move { From = new Vector2(7, 7), To = new Vector2(8, 6) }, + // P1 Bishop takes P2 Lance + new Move { From = new Vector2(6, 6), To = new Vector2(8, 8) }, + // P2 Bishop + new Move { From = new Vector2(8, 6), To = new Vector2(7, 7) }, + // P1 arbitrary move + new Move { From = new Vector2(0, 3), To = new Vector2(0, 4) }, + // P2 Bishop, check + new Move { From = new Vector2(7, 7), To = new Vector2(2, 2) }, + }; + var shogi = new ShogiBoard(moves); + // Prerequisites + shogi.InCheck.Should().Be(WhichPlayer.Player1); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + + // Act - P1 tries to place a Lance while in check. + var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(4, 4) }); + + // Assert + dropSuccess.Should().BeFalse(); + shogi.Board[4, 4].Should().BeNull(); + shogi.InCheck.Should().Be(WhichPlayer.Player1); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + } + + [TestMethod] + public void PreventInvalidDrop_Capture() + { + // Arrange + var moves = new[] + { + // P1 Pawn + new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + // P2 Pawn + new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) }, + // P1 Bishop, capture P2 Pawn, check + new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }, + // P2 Gold, block check + new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) }, + // P1 Bishop capture P2 Bishop + new Move { From = new Vector2(6, 6), To = new Vector2(7, 7) }, + // P2 arbitrary move + new Move { From = new Vector2(0, 8), To = new Vector2(0, 7) }, + }; + var shogi = new ShogiBoard(moves); + + // Prerequisites + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + + // Act - P1 tries to place Bishop from hand to an already-occupied position + var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Bishop, To = new Vector2(4, 0) }); + + // Assert + dropSuccess.Should().BeFalse(); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.Board[4, 0].WhichPiece.Should().Be(WhichPiece.King); } [TestMethod] diff --git a/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs b/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs index 935051e..089d142 100644 --- a/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs +++ b/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs @@ -1,8 +1,7 @@ using FluentAssertions; -using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.BoardState.Pieces; using Microsoft.VisualStudio.TestTools.UnitTesting; using PathFinding; -using System.Collections.Generic; using System.Numerics; namespace Gameboard.ShogiUI.UnitTests.PathFinding @@ -10,32 +9,25 @@ namespace Gameboard.ShogiUI.UnitTests.PathFinding [TestClass] public class PathFinder2DShould { - class TestElement : IPlanarElement - { - public ICollection GetPaths() => throw new System.NotImplementedException(); - public bool IsUpsideDown => false; - } - [TestMethod] public void Maths() { - var finder = new PathFinder2D(new Array2D(5, 5)); - var result = finder.IsPathable( + var result = PathFinder2D.IsPathable( new Vector2(2, 2), new Vector2(7, 7), new Vector2(1, 1) ); result.Should().BeTrue(); - result = finder.IsPathable( + result = PathFinder2D.IsPathable( new Vector2(2, 2), new Vector2(7, 7), new Vector2(0, 0) ); result.Should().BeFalse(); - result = finder.IsPathable( + result = PathFinder2D.IsPathable( new Vector2(2, 2), new Vector2(7, 7), new Vector2(-1, 1) diff --git a/PathFinding/Direction.cs b/PathFinding/Direction.cs new file mode 100644 index 0000000..2ee825d --- /dev/null +++ b/PathFinding/Direction.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace PathFinding +{ + public static class Direction + { + public static readonly Vector2 Up = new(0, 1); + public static readonly Vector2 Down = new(0, -1); + public static readonly Vector2 Left = new(-1, 0); + public static readonly Vector2 Right = new(1, 0); + public static readonly Vector2 UpLeft = new(-1, 1); + public static readonly Vector2 UpRight = new(1, 1); + public static readonly Vector2 DownLeft = new(-1, -1); + public static readonly Vector2 DownRight = new(1, -1); + public static readonly Vector2 KnightLeft = new(-1, 2); + public static readonly Vector2 KnightRight = new(1, 2); + } +} diff --git a/PathFinding/Distance.cs b/PathFinding/Distance.cs new file mode 100644 index 0000000..339296d --- /dev/null +++ b/PathFinding/Distance.cs @@ -0,0 +1,8 @@ +namespace PathFinding +{ + public enum Distance + { + OneStep, + MultiStep + } +} \ No newline at end of file diff --git a/PathFinding/Enums.cs b/PathFinding/Enums.cs deleted file mode 100644 index fab3f18..0000000 --- a/PathFinding/Enums.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace PathFinding -{ - public enum HaltCondition - { - /// - /// Do not stop until you reach the collection boundary. - /// - None, - /// - /// Halt after encountering a non-null element. - /// - AfterCollide - } -} -public enum Distance -{ - OneStep, - MultiStep -} diff --git a/PathFinding/IPlanarCollection.cs b/PathFinding/IPlanarCollection.cs index 95506e1..d276b12 100644 --- a/PathFinding/IPlanarCollection.cs +++ b/PathFinding/IPlanarCollection.cs @@ -2,7 +2,7 @@ namespace PathFinding { - public interface IPlanarCollection : IEnumerable + public interface IPlanarCollection : IEnumerable where T : IPlanarElement { T this[float x, float y] { get; set; } int GetLength(int dimension); diff --git a/PathFinding/IPlanarElement.cs b/PathFinding/IPlanarElement.cs index 4983bb4..5d3fde4 100644 --- a/PathFinding/IPlanarElement.cs +++ b/PathFinding/IPlanarElement.cs @@ -1,12 +1,9 @@  -using System.Collections.Generic; - namespace PathFinding { public interface IPlanarElement { - ICollection GetPaths(); - + MoveSet MoveSet { get; } bool IsUpsideDown { get; } } } diff --git a/PathFinding/Path.cs b/PathFinding/Move.cs similarity index 77% rename from PathFinding/Path.cs rename to PathFinding/Move.cs index 4b961bb..5ff2c8e 100644 --- a/PathFinding/Path.cs +++ b/PathFinding/Move.cs @@ -4,11 +4,11 @@ using System.Numerics; namespace PathFinding { [DebuggerDisplay("{Direction} - {Distance}")] - public class Path + public class Move { public Vector2 Direction { get; } public Distance Distance { get; } - public Path(Vector2 direction, Distance distance = Distance.OneStep) + public Move(Vector2 direction, Distance distance = Distance.OneStep) { Direction = direction; Distance = distance; diff --git a/PathFinding/MoveSet.cs b/PathFinding/MoveSet.cs new file mode 100644 index 0000000..f6c7260 --- /dev/null +++ b/PathFinding/MoveSet.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace PathFinding +{ + public class MoveSet + { + private readonly IPlanarElement element; + private readonly ICollection moves; + private readonly ICollection upsidedownMoves; + + public MoveSet(IPlanarElement element, ICollection moves) + { + this.element = element; + this.moves = moves; + upsidedownMoves = moves.Select(_ => new Move(Vector2.Negate(_.Direction), _.Distance)).ToList(); + } + + public ICollection GetMoves() => element.IsUpsideDown ? upsidedownMoves : moves; + + } +} diff --git a/PathFinding/PathFinder2D.cs b/PathFinding/PathFinder2D.cs index 539ae7e..ae32b6b 100644 --- a/PathFinding/PathFinder2D.cs +++ b/PathFinding/PathFinder2D.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -13,7 +12,6 @@ namespace PathFinding /// public delegate void Callback(T collider, Vector2 position); - private readonly IPlanarCollection collection; private readonly int width; private readonly int height; @@ -39,8 +37,9 @@ namespace PathFinding return false; } var element = collection[origin.X, origin.Y]; - var path = FindDirectionTowardsDestination(element.GetPaths(), origin, destination); + if (element == null) return false; + var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination); if (!IsPathable(origin, destination, path.Direction)) { // Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen. @@ -69,7 +68,7 @@ namespace PathFinding public void PathEvery(Vector2 from, Callback callback) { var element = collection[from.X, from.Y]; - foreach (var path in element.GetPaths()) + foreach (var path in element.MoveSet.GetMoves()) { var shouldPath = true; var next = Vector2.Add(from, path.Direction); ; @@ -106,16 +105,15 @@ namespace PathFinding } } - public Path FindDirectionTowardsDestination(ICollection paths, Vector2 origin, Vector2 destination) => + public static Move FindDirectionTowardsDestination(ICollection paths, Vector2 origin, Vector2 destination) => paths.Aggregate((a, b) => Vector2.Distance(destination, Vector2.Add(origin, a.Direction)) < Vector2.Distance(destination, Vector2.Add(origin, b.Direction)) ? a : b); - - public bool IsPathable(Vector2 origin, Vector2 destination, T element) + public static bool IsPathable(Vector2 origin, Vector2 destination, T element) { - var path = FindDirectionTowardsDestination(element.GetPaths(), origin, destination); + var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination); return IsPathable(origin, destination, path.Direction); } - public bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction) + public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction) { direction = Vector2.Normalize(direction); var next = Vector2.Add(origin, direction); diff --git a/PathFinding/PathFinding.csproj b/PathFinding/PathFinding.csproj index 423f013..ee29921 100644 --- a/PathFinding/PathFinding.csproj +++ b/PathFinding/PathFinding.csproj @@ -3,6 +3,7 @@ net5.0 true + 5