From 640db4f4a2026524d3857c369d6c04e7e8b640aa Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Thu, 25 Feb 2021 19:55:43 -0600 Subject: [PATCH] before remove validation board --- Benchmarking/Benchmarks.cs | 15 ++- Gameboard.ShogiUI.BoardState/Array2D.cs | 51 ++++++++- Gameboard.ShogiUI.BoardState/Piece.cs | 8 ++ Gameboard.ShogiUI.BoardState/ShogiBoard.cs | 124 ++++++++++++--------- Gameboard.ShogiUI.Sockets.sln | 8 +- PathFinding/IPlanarCollection.cs | 7 ++ PathFinding/PathFinder2D.cs | 10 ++ PathFinding/PathFinding.csproj | 7 ++ 8 files changed, 172 insertions(+), 58 deletions(-) create mode 100644 PathFinding/IPlanarCollection.cs create mode 100644 PathFinding/PathFinder2D.cs create mode 100644 PathFinding/PathFinding.csproj diff --git a/Benchmarking/Benchmarks.cs b/Benchmarking/Benchmarks.cs index 8cb0685..208b962 100644 --- a/Benchmarking/Benchmarks.cs +++ b/Benchmarking/Benchmarks.cs @@ -7,7 +7,7 @@ namespace Benchmarking { public class Benchmarks { - private Move[] moves; + private readonly Move[] moves; public Benchmarks() { @@ -37,7 +37,7 @@ namespace Benchmarking } [Benchmark] - public void OnlyValidMoves_NewBoard() + public void One() { var board = new ShogiBoard(); foreach (var move in moves) @@ -45,6 +45,17 @@ namespace Benchmarking board.TryMove(move); } } + + [Benchmark] + public void Two() + { + var board = new ShogiBoard(); + foreach (var move in moves) + { + //board.TryMove2(move); + } + } + } public class Program diff --git a/Gameboard.ShogiUI.BoardState/Array2D.cs b/Gameboard.ShogiUI.BoardState/Array2D.cs index f2d7e0c..623a536 100644 --- a/Gameboard.ShogiUI.BoardState/Array2D.cs +++ b/Gameboard.ShogiUI.BoardState/Array2D.cs @@ -1,20 +1,21 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Gameboard.ShogiUI.BoardState { - public class Array2D : IEnumerable + public class Array2D : IEnumerable { + /// 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) { this.width = width; + this.height = height; array = new T[width * height]; } @@ -24,6 +25,48 @@ namespace Gameboard.ShogiUI.BoardState set => array[y * width + x] = value; } + 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++) + { + for (var y = 0; y < height; y++) + { + if (this[x, y] != null) + callback(this[x, y], x, y); + } + } + } + + // TODO: Figure out a better return type, or make this class specific to ShogiBoard. + public BoardVector 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 BoardVector(x, y); + } + } + return null; + } + + public IEnumerator GetEnumerator() + { + foreach (var item in array) yield return item; + } + IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator(); } } diff --git a/Gameboard.ShogiUI.BoardState/Piece.cs b/Gameboard.ShogiUI.BoardState/Piece.cs index 9a7ff64..99b218d 100644 --- a/Gameboard.ShogiUI.BoardState/Piece.cs +++ b/Gameboard.ShogiUI.BoardState/Piece.cs @@ -33,6 +33,14 @@ namespace Gameboard.ShogiUI.BoardState _ => " ? ", }; + public bool IsRanged => WhichPiece switch + { + WhichPiece.Bishop => true, + WhichPiece.Rook => true, + WhichPiece.Lance => !IsPromoted, + _ => false, + }; + public void ToggleOwnership() { Owner = Owner == WhichPlayer.Player1 diff --git a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs index 0d45761..9b1ca78 100644 --- a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs +++ b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Numerics; using System.Text; namespace Gameboard.ShogiUI.BoardState @@ -13,9 +14,10 @@ namespace Gameboard.ShogiUI.BoardState { private delegate void MoveSetCallback(Piece piece, BoardVector position); private ShogiBoard validationBoard; - + private Vector2 player1King; + private Vector2 player2King; public IReadOnlyDictionary> Hands { get; } - public Piece[,] Board { get; } + public Array2D Board { get; } public List MoveHistory { get; } public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2; public WhichPlayer? InCheck { get; private set; } @@ -23,13 +25,15 @@ namespace Gameboard.ShogiUI.BoardState public ShogiBoard() { - Board = new Piece[9, 9]; + Board = new Array2D(9, 9); MoveHistory = new List(20); Hands = new Dictionary> { { WhichPlayer.Player1, new List()}, { WhichPlayer.Player2, new List()}, }; InitializeBoardState(); + player1King = new Vector2(4, 0); + player2King = new Vector2(4, 8); } public ShogiBoard(IList moves) : this() { @@ -45,7 +49,40 @@ namespace Gameboard.ShogiUI.BoardState /// /// Attempts a given move. Returns false if the move is illegal. /// - public bool TryMove(Move move) + //public bool TryMove2(Move move) + //{ + // // Try making the move in a "throw away" board. + // if (validationBoard == null) + // { + // validationBoard = new ShogiBoard(MoveHistory); + // } + // var isValid = move.PieceFromCaptured.HasValue + // ? validationBoard.PlaceFromHand(move) + // : validationBoard.PlaceFromBoard(move); + // if (!isValid) + // { + // // Invalidate the "throw away" board. + // validationBoard = null; + // return false; + // } + // // Assert that this move does not put the moving player in check. + // if (validationBoard.EvaluateCheck(WhoseTurn)) return false; + + // var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; + // // The move is valid and legal; update board state. + // if (move.PieceFromCaptured.HasValue) PlaceFromHand(move); + // else PlaceFromBoard(move); + + // // Evaluate check + // InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null; + // if (InCheck.HasValue) + // { + // //IsCheckmate = EvaluateCheckmate(); + // } + // return true; + //} + + public bool TryMove(Move move, bool shouldEvaluateCheck = true) { // Try making the move in a "throw away" board. if (validationBoard == null) @@ -70,14 +107,18 @@ namespace Gameboard.ShogiUI.BoardState else PlaceFromBoard(move); // Evaluate check - InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null; - if (InCheck.HasValue) + if (shouldEvaluateCheck) { - //IsCheckmate = EvaluateCheckmate(); + InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null; + if (InCheck.HasValue) + { + IsCheckmate = EvaluateCheckmate(); + } } return true; } + private bool EvaluateCheckmate() { if (!InCheck.HasValue) return false; @@ -100,10 +141,9 @@ namespace Gameboard.ShogiUI.BoardState // And evaluate if any move gets the player out of check. foreach (var position in positionsToCheck) { - var moveSuccess = validationBoard.TryMove(new Move { From = from, To = position }); + var moveSuccess = validationBoard.TryMove(new Move { From = from, To = position }, false); if (moveSuccess) { - Console.WriteLine($"Not check mate"); isCheckmate &= validationBoard.EvaluateCheck(InCheck.Value); validationBoard = null; } @@ -148,7 +188,7 @@ namespace Gameboard.ShogiUI.BoardState var fromPiece = Board[move.From.X, move.From.Y]; if (fromPiece == null) return false; // Invalid move if (fromPiece.Owner != WhoseTurn) return false; // Invalid move; cannot move other players pieces. - if (ValidateMoveAgainstMoveSet(move) == false) return false; // Invalid move; move not part of move-set. + if (ValidateMoveAgainstMoveSet(move.From, move.To) == false) return false; // Invalid move; move not part of move-set. var captured = Board[move.To.X, move.To.Y]; if (captured != null) @@ -172,6 +212,19 @@ namespace Gameboard.ShogiUI.BoardState } Board[move.To.X, move.To.Y] = fromPiece; Board[move.From.X, move.From.Y] = null; + if (fromPiece.WhichPiece == WhichPiece.King) + { + if (fromPiece.Owner == WhichPlayer.Player1) + { + player1King.X = move.To.X; + player1King.Y = move.To.Y; + } + else if (fromPiece.Owner == WhichPlayer.Player2) + { + player2King.X = move.To.X; + player2King.Y = move.To.Y; + } + } MoveHistory.Add(move); return true; } @@ -215,13 +268,16 @@ namespace Gameboard.ShogiUI.BoardState /// private bool EvaluateCheck(WhichPlayer whichPlayer) { + var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King; var inCheck = false; // Iterate every board piece... Board.ForEachNotNull((piece, x, y) => { - // ...that belongs to the opponent... - if (piece.Owner != whichPlayer) + var v = new Vector2(x, y); + // ...that belongs to the opponent within range... + if (piece.Owner != whichPlayer && (piece.IsRanged || Vector2.Distance(kingPosition, v) < 3)) { + Console.WriteLine($"Evaluating {piece.WhichPiece}"); IterateMoveSet(new BoardVector(x, y), (threatenedPiece, position) => { // ...and threatens the player's king. @@ -233,47 +289,13 @@ namespace Gameboard.ShogiUI.BoardState }); return inCheck; } - private bool EvaluateCheck2(WhichPlayer whichPlayer) - { - var inCheck = false; - MoveSetCallback checkKingThreat = (piece, position) => - { - inCheck |= - piece?.WhichPiece == WhichPiece.King - && piece?.Owner == whichPlayer; - }; - // Find interesting pieces - var longRangePiecePositions = new List(8); - Board.ForEachNotNull((piece, x, y) => - { - if (piece.Owner != whichPlayer) - { - switch (piece.WhichPiece) - { - case WhichPiece.Bishop: - case WhichPiece.Rook: - longRangePiecePositions.Add(new BoardVector(x, y)); - break; - case WhichPiece.Lance: - if (!piece.IsPromoted) longRangePiecePositions.Add(new BoardVector(x, y)); - break; - } - } - }); - - foreach(var position in longRangePiecePositions) - { - IterateMoveSet(position, checkKingThreat); - } - - return inCheck; - } - private bool ValidateMoveAgainstMoveSet(Move move) + + private bool ValidateMoveAgainstMoveSet(BoardVector from, BoardVector to) { var isValid = false; - IterateMoveSet(move.From, (piece, position) => + IterateMoveSet(from, (piece, position) => { - if (piece?.Owner != WhoseTurn && position == move.To) + if (piece?.Owner != WhoseTurn && position == to) { isValid = true; } @@ -289,7 +311,7 @@ namespace Gameboard.ShogiUI.BoardState // TODO: Make these are of the move To, so only possible moves towards the move To are iterated. // Maybe separate functions? Sometimes I need to iterate the whole move-set, sometimes I need to iterate only the move-set towards the move To. var piece = Board[from.X, from.Y]; - switch (piece.WhichPiece) + switch (piece?.WhichPiece) { case WhichPiece.King: IterateKingMoveSet(from, callback); diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln index 38ccb7d..497525d 100644 --- a/Gameboard.ShogiUI.Sockets.sln +++ b/Gameboard.ShogiUI.Sockets.sln @@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.BoardStat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests", "Gameboard.ShogiUI.UnitTests\Gameboard.ShogiUI.UnitTests.csproj", "{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -41,6 +43,10 @@ Global {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU + {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PathFinding/IPlanarCollection.cs b/PathFinding/IPlanarCollection.cs new file mode 100644 index 0000000..8a2b046 --- /dev/null +++ b/PathFinding/IPlanarCollection.cs @@ -0,0 +1,7 @@ +namespace PathFinding +{ + interface IPlanarCollection + { + T this[int x, int y] { get; set; } + } +} diff --git a/PathFinding/PathFinder2D.cs b/PathFinding/PathFinder2D.cs new file mode 100644 index 0000000..93b9bc0 --- /dev/null +++ b/PathFinding/PathFinder2D.cs @@ -0,0 +1,10 @@ + +namespace PathFinding +{ + public class PathFinder2D + { + public PathFinder2D() + { + } + } +} diff --git a/PathFinding/PathFinding.csproj b/PathFinding/PathFinding.csproj new file mode 100644 index 0000000..f208d30 --- /dev/null +++ b/PathFinding/PathFinding.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + +