From 05a9c71499e3f1025e8c7d2769a20a852741754b Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Tue, 6 Apr 2021 19:52:02 -0500 Subject: [PATCH] Fixed accidentally building the board from player2 perspective. --- Benchmarking/Benchmarking.csproj | 2 +- Benchmarking/Benchmarks.cs | 2 +- ....csproj => Gameboard.ShogiUI.Rules.csproj} | 0 Gameboard.ShogiUI.BoardState/Move.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs | 2 +- .../Pieces/GoldGeneral.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/King.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Knight.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Lance.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Piece.cs | 2 +- Gameboard.ShogiUI.BoardState/Pieces/Rook.cs | 2 +- .../Pieces/SilverGeneral.cs | 2 +- .../PlanarCollection.cs | 2 +- Gameboard.ShogiUI.BoardState/ShogiBoard.cs | 61 ++++-- Gameboard.ShogiUI.BoardState/WhichPiece.cs | 2 +- Gameboard.ShogiUI.BoardState/WhichPlayer.cs | 2 +- Gameboard.ShogiUI.Domain/Entities/Board.cs | 6 + Gameboard.ShogiUI.Domain/Entities/Match.cs | 30 +++ .../Gameboard.ShogiUI.Domain.csproj | 7 + .../ValueObjects/Class1.cs | 15 ++ .../Socket/Messages/LoadGame.cs | 2 +- .../Socket/Messages/Move.cs | 2 +- .../Socket/Types/Piece.cs | 5 - Gameboard.ShogiUI.Sockets.sln | 10 +- .../Controllers/GameController.cs | 4 +- .../Controllers/SocketController.cs | 11 +- .../Gameboard.ShogiUI.Sockets.csproj | 2 +- .../Managers/BoardManager.cs | 7 +- .../ClientActionHandlers/CreateGameHandler.cs | 9 +- .../ClientActionHandlers/JoinByCodeHandler.cs | 38 ++-- .../ClientActionHandlers/JoinGameHandler.cs | 4 +- .../ClientActionHandlers/LoadGameHandler.cs | 15 +- .../ClientActionHandlers/MoveHandler.cs | 54 +++-- .../Models/BoardState.cs | 40 ++++ Gameboard.ShogiUI.Sockets/Models/Move.cs | 8 +- Gameboard.ShogiUI.Sockets/Models/Piece.cs | 27 +++ Gameboard.ShogiUI.Sockets/Models/Player.cs | 12 ++ .../Properties/launchSettings.json | 2 +- .../Repositories/GameboardRepository.cs | 79 +++++--- .../GameboardRepositoryManager.cs | 15 +- .../BoardStateExtensions.cs | 8 +- .../{BoardState => Rules}/ShogiBoardShould.cs | 190 +++++++++--------- PathFinding/Direction.cs | 16 +- PathFinding/PathFinder2D.cs | 8 - 45 files changed, 441 insertions(+), 276 deletions(-) rename Gameboard.ShogiUI.BoardState/{Gameboard.ShogiUI.BoardState.csproj => Gameboard.ShogiUI.Rules.csproj} (100%) create mode 100644 Gameboard.ShogiUI.Domain/Entities/Board.cs create mode 100644 Gameboard.ShogiUI.Domain/Entities/Match.cs create mode 100644 Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj create mode 100644 Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs create mode 100644 Gameboard.ShogiUI.Sockets/Models/BoardState.cs create mode 100644 Gameboard.ShogiUI.Sockets/Models/Piece.cs create mode 100644 Gameboard.ShogiUI.Sockets/Models/Player.cs rename Gameboard.ShogiUI.UnitTests/{BoardState => Rules}/BoardStateExtensions.cs (91%) rename Gameboard.ShogiUI.UnitTests/{BoardState => Rules}/ShogiBoardShould.cs (74%) diff --git a/Benchmarking/Benchmarking.csproj b/Benchmarking/Benchmarking.csproj index 470d7e6..ec8dd8e 100644 --- a/Benchmarking/Benchmarking.csproj +++ b/Benchmarking/Benchmarking.csproj @@ -11,7 +11,7 @@ - + diff --git a/Benchmarking/Benchmarks.cs b/Benchmarking/Benchmarks.cs index 9969cb8..b5785ed 100644 --- a/Benchmarking/Benchmarks.cs +++ b/Benchmarking/Benchmarks.cs @@ -1,7 +1,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Running; -using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.Rules; using System; using System.Linq; using System.Numerics; diff --git a/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj b/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.Rules.csproj similarity index 100% rename from Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.BoardState.csproj rename to Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.Rules.csproj diff --git a/Gameboard.ShogiUI.BoardState/Move.cs b/Gameboard.ShogiUI.BoardState/Move.cs index fc8f860..693c954 100644 --- a/Gameboard.ShogiUI.BoardState/Move.cs +++ b/Gameboard.ShogiUI.BoardState/Move.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Numerics; -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.Rules { [DebuggerDisplay("{From} - {To}")] public class Move diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs b/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs index f91e6ea..3c433cc 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class Bishop : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs b/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs index 121903d..055b779 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class GoldenGeneral : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/King.cs b/Gameboard.ShogiUI.BoardState/Pieces/King.cs index b799a06..ab4d9c4 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/King.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/King.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class King : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs b/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs index 5a6cdd1..7091ceb 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class Knight : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs b/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs index 0329ee1..48be6a2 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class Lance : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs b/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs index 4710bc3..1005c6f 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class Pawn : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs b/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs index 071d301..1f019b2 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Diagnostics; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { [DebuggerDisplay("{WhichPiece} {Owner}")] public abstract class Piece : IPlanarElement diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs b/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs index 709c82f..3a990a4 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class Rook : Piece { diff --git a/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs b/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs index 557c3b0..3287604 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs @@ -1,7 +1,7 @@ using PathFinding; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState.Pieces +namespace Gameboard.ShogiUI.Rules.Pieces { public class SilverGeneral : Piece { diff --git a/Gameboard.ShogiUI.BoardState/PlanarCollection.cs b/Gameboard.ShogiUI.BoardState/PlanarCollection.cs index f093035..4baf867 100644 --- a/Gameboard.ShogiUI.BoardState/PlanarCollection.cs +++ b/Gameboard.ShogiUI.BoardState/PlanarCollection.cs @@ -3,7 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.Rules { public class PlanarCollection : IPlanarCollection, IEnumerable where T : IPlanarElement { diff --git a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs index 8c3653b..0449668 100644 --- a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs +++ b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs @@ -1,10 +1,10 @@ -using Gameboard.ShogiUI.BoardState.Pieces; +using Gameboard.ShogiUI.Rules.Pieces; using PathFinding; using System; using System.Collections.Generic; using System.Numerics; -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.Rules { /// /// Facilitates Shogi board state transitions, cognisant of Shogi rules. @@ -14,16 +14,20 @@ namespace Gameboard.ShogiUI.BoardState public class ShogiBoard { private delegate void MoveSetCallback(Piece piece, Vector2 position); + private readonly bool isValidationBoard; private readonly PathFinder2D pathFinder; private ShogiBoard validationBoard; private Vector2 player1King; private Vector2 player2King; public IReadOnlyDictionary> Hands { get; } - public PlanarCollection Board { get; } + public PlanarCollection Board { get; } //TODO: Hide this being a getter method public List MoveHistory { get; } public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2; public WhichPlayer? InCheck { get; private set; } public bool IsCheckmate { get; private set; } + + + public string Error { get; private set; } public ShogiBoard() { @@ -35,8 +39,8 @@ namespace Gameboard.ShogiUI.BoardState }; pathFinder = new PathFinder2D(Board); InitializeBoardState(); - player1King = new Vector2(4, 0); - player2King = new Vector2(4, 8); + player1King = new Vector2(4, 8); + player2King = new Vector2(4, 0); } public ShogiBoard(IList moves) : this() @@ -46,13 +50,14 @@ namespace Gameboard.ShogiUI.BoardState if (!Move(moves[i])) { // Todo: Add some smarts to know why a move was invalid. In check? Piece not found? etc. - throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}."); + throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}. {Error}"); } } } private ShogiBoard(ShogiBoard toCopy) { + isValidationBoard = true; Board = new PlanarCollection(9, 9); for (var x = 0; x < 9; x++) for (var y = 0; y < 9; y++) @@ -143,8 +148,8 @@ namespace Gameboard.ShogiUI.BoardState minimumY = WhoseTurn == WhichPlayer.Player1 ? 7 : 1; break; } - if (WhoseTurn == WhichPlayer.Player1 && move.To.Y > minimumY) return false; - if (WhoseTurn == WhichPlayer.Player2 && move.To.Y < minimumY) return false; + if (WhoseTurn == WhichPlayer.Player1 && move.To.Y < minimumY) return false; + if (WhoseTurn == WhichPlayer.Player2 && move.To.Y > minimumY) return false; // Mutate the board. Board[move.To.X, move.To.Y] = Hands[WhoseTurn][index]; @@ -156,9 +161,21 @@ namespace Gameboard.ShogiUI.BoardState private bool PlaceFromBoard(Move move) { 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 (IsPathable(move.From, move.To) == false) return false; // Invalid move; move not part of move-set. + if (fromPiece == null) + { + Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}."; + return false; // Invalid move + } + if (fromPiece.Owner != WhoseTurn) + { + Error = "Not allowed to move the opponents piece"; + return false; // Invalid move; cannot move other players pieces. + } + if (IsPathable(move.From, move.To) == false) + { + Error = $"Illegal move for {fromPiece.WhichPiece}. {nameof(move)}.{nameof(move.To)} is not part of the move-set."; + return false; // Invalid move; move not part of move-set. + } var captured = Board[move.To.X, move.To.Y]; if (captured != null) @@ -171,11 +188,11 @@ namespace Gameboard.ShogiUI.BoardState //Mutate the board. if (move.IsPromotion) { - if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y > 5 || move.From.Y > 5)) + if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y < 3 || move.From.Y < 3)) { fromPiece.Promote(); } - else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y < 3 || move.From.Y < 3)) + else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y > 5 || move.From.Y > 5)) { fromPiece.Promote(); } @@ -313,12 +330,12 @@ namespace Gameboard.ShogiUI.BoardState } private void ResetFrontRow(WhichPlayer player) { - int y = player == WhichPlayer.Player1 ? 2 : 6; + int y = player == WhichPlayer.Player1 ? 6 : 2; for (int x = 0; x < 9; x++) Board[x, y] = new Pawn(player); } private void ResetMiddleRow(WhichPlayer player) { - int y = player == WhichPlayer.Player1 ? 1 : 7; + int y = player == WhichPlayer.Player1 ? 7 : 1; Board[0, y] = null; for (int x = 2; x < 7; x++) Board[x, y] = null; @@ -336,7 +353,7 @@ namespace Gameboard.ShogiUI.BoardState } private void ResetRearRow(WhichPlayer player) { - int y = player == WhichPlayer.Player1 ? 0 : 8; + int y = player == WhichPlayer.Player1 ? 8 : 0; Board[0, y] = new Lance(player); Board[1, y] = new Knight(player); @@ -350,13 +367,13 @@ namespace Gameboard.ShogiUI.BoardState } private void InitializeBoardState() { - ResetRearRow(WhichPlayer.Player1); - ResetMiddleRow(WhichPlayer.Player1); - ResetFrontRow(WhichPlayer.Player1); - ResetEmptyRows(); - ResetFrontRow(WhichPlayer.Player2); - ResetMiddleRow(WhichPlayer.Player2); ResetRearRow(WhichPlayer.Player2); + ResetMiddleRow(WhichPlayer.Player2); + ResetFrontRow(WhichPlayer.Player2); + ResetEmptyRows(); + ResetFrontRow(WhichPlayer.Player1); + ResetMiddleRow(WhichPlayer.Player1); + ResetRearRow(WhichPlayer.Player1); } #endregion } diff --git a/Gameboard.ShogiUI.BoardState/WhichPiece.cs b/Gameboard.ShogiUI.BoardState/WhichPiece.cs index a0dd88c..ec4fd4d 100644 --- a/Gameboard.ShogiUI.BoardState/WhichPiece.cs +++ b/Gameboard.ShogiUI.BoardState/WhichPiece.cs @@ -1,4 +1,4 @@ -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.Rules { public enum WhichPiece { diff --git a/Gameboard.ShogiUI.BoardState/WhichPlayer.cs b/Gameboard.ShogiUI.BoardState/WhichPlayer.cs index 1e8de13..4b8b8f2 100644 --- a/Gameboard.ShogiUI.BoardState/WhichPlayer.cs +++ b/Gameboard.ShogiUI.BoardState/WhichPlayer.cs @@ -1,4 +1,4 @@ -namespace Gameboard.ShogiUI.BoardState +namespace Gameboard.ShogiUI.Rules { public enum WhichPlayer { diff --git a/Gameboard.ShogiUI.Domain/Entities/Board.cs b/Gameboard.ShogiUI.Domain/Entities/Board.cs new file mode 100644 index 0000000..9533d8d --- /dev/null +++ b/Gameboard.ShogiUI.Domain/Entities/Board.cs @@ -0,0 +1,6 @@ +namespace Gameboard.ShogiUI.Domain +{ + public class Board + { + } +} diff --git a/Gameboard.ShogiUI.Domain/Entities/Match.cs b/Gameboard.ShogiUI.Domain/Entities/Match.cs new file mode 100644 index 0000000..2d54945 --- /dev/null +++ b/Gameboard.ShogiUI.Domain/Entities/Match.cs @@ -0,0 +1,30 @@ +namespace Gameboard.ShogiUI.Domain +{ + public class Match + { + public string Name { get; } + public string Player1 { get; } + public string Player2 { get; } + + /// + /// Initialize pre-existing Match. + /// + public Match(MatchMeta meta, Board board) + { + Name = meta.Name; + Player1 = meta.Player1; + Player2 = meta.Player2; + } + + /// + /// Create a new Match. + /// + public Match(string name, string player1) + { + Name = name; + Player1 = player1; + } + + + } +} diff --git a/Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj b/Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj new file mode 100644 index 0000000..f208d30 --- /dev/null +++ b/Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs b/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs new file mode 100644 index 0000000..6939def --- /dev/null +++ b/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Gameboard.ShogiUI.Domain +{ + public class MatchMeta + { + public string Name { get; } + public string Player1 { get; } + public string Player2 { get; } + } +} diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs index c457791..981ebf6 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs @@ -14,7 +14,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages { public string Action { get; private set; } public Game Game { get; set; } - public IReadOnlyList Moves { get; set; } + public BoardState BoardState { get; set; } public string Error { get; set; } public LoadGameResponse(ClientAction action) diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs index 5039196..e46f9a0 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs @@ -15,7 +15,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages public string Action { get; } public string Error { get; set; } public string GameName { get; set; } - public Move Move { get; set; } + public BoardState BoardState { get; set; } public string PlayerName { get; set; } public MoveResponse(ClientAction action) diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs index bb5ef62..8f9fd23 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs @@ -4,11 +4,6 @@ { public WhichPiece WhichPiece { get; set; } - /// - /// True if this piece is controlled by you. - /// - public bool IsControlledByMe { get; set; } - public bool IsPromoted { get; set; } } } diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln index 497525d..76d0058 100644 --- a/Gameboard.ShogiUI.Sockets.sln +++ b/Gameboard.ShogiUI.Sockets.sln @@ -9,13 +9,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets.S EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.BoardState", "Gameboard.ShogiUI.BoardState\Gameboard.ShogiUI.BoardState.csproj", "{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Rules", "Gameboard.ShogiUI.BoardState\Gameboard.ShogiUI.Rules.csproj", "{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests", "Gameboard.ShogiUI.UnitTests\Gameboard.ShogiUI.UnitTests.csproj", "{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}" EndProject 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gameboard.ShogiUI.Domain", "Gameboard.ShogiUI.Domain\Gameboard.ShogiUI.Domain.csproj", "{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -47,6 +49,10 @@ Global {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 + {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs index 5d0becd..72a1fa8 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs @@ -31,7 +31,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers var isPlayer1 = await manager.IsPlayer1(request.SessionName, userName); if (isPlayer1) { - var code = (await repository.PostJoinCode(request.SessionName, userName)).JoinCode; + var code = await repository.PostJoinCode(request.SessionName, userName); return new CreatedResult("", new PostGameInvitationResponse(code)); } else @@ -49,7 +49,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers var isPlayer1 = manager.IsPlayer1(request.SessionName, request.GuestId); if (isGuest && await isPlayer1) { - var code = (await repository.PostJoinCode(request.SessionName, request.GuestId)).JoinCode; + var code = await repository.PostJoinCode(request.SessionName, request.GuestId); return new CreatedResult("", new PostGameInvitationResponse(code)); } else diff --git a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs index a729cdb..4891963 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs @@ -1,5 +1,4 @@ using Gameboard.ShogiUI.Sockets.Managers; -using Gameboard.ShogiUI.Sockets.Repositories; using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers; using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages; using Microsoft.AspNetCore.Authorization; @@ -15,16 +14,13 @@ namespace Gameboard.ShogiUI.Sockets.Controllers public class SocketController : ControllerBase { private readonly ISocketTokenManager tokenManager; - private readonly IGameboardRepository gameboardRepository; private readonly IGameboardRepositoryManager gameboardManager; public SocketController( ISocketTokenManager tokenManager, - IGameboardRepository gameboardRepository, IGameboardRepositoryManager gameboardManager) { this.tokenManager = tokenManager; - this.gameboardRepository = gameboardRepository; this.gameboardManager = gameboardManager; } @@ -48,11 +44,10 @@ namespace Gameboard.ShogiUI.Sockets.Controllers } else { - var response = await gameboardRepository.GetPlayer(request.ClientId); - if (response != null && response.Player != null) + if (await gameboardManager.PlayerExists(request.ClientId)) { - var token = tokenManager.GenerateToken(response.Player.Name); - return new JsonResult(new GetGuestTokenResponse(response.Player.Name, token)); + var token = tokenManager.GenerateToken(request.ClientId); + return new JsonResult(new GetGuestTokenResponse(request.ClientId, token)); } } return new UnauthorizedResult(); diff --git a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj index 3b3a6f8..dd477c5 100644 --- a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj +++ b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj @@ -17,7 +17,7 @@ - + diff --git a/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs index 494c738..813887e 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs @@ -1,4 +1,4 @@ -using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.Rules; using System.Collections.Concurrent; namespace Gameboard.ShogiUI.Sockets.Managers @@ -26,10 +26,5 @@ namespace Gameboard.ShogiUI.Sockets.Managers return board; return null; } - - public string GetBoardState() - { - return string.Empty; - } } } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs index d8244a7..bd93ab6 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs @@ -12,16 +12,13 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers // It can be an API route and still tell socket connections about the new session. public class CreateGameHandler : IActionHandler { - private readonly ILogger logger; private readonly IGameboardRepository repository; private readonly ISocketCommunicationManager communicationManager; public CreateGameHandler( - ILogger logger, ISocketCommunicationManager communicationManager, IGameboardRepository repository) { - this.logger = logger; this.repository = repository; this.communicationManager = communicationManager; } @@ -29,7 +26,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers public async Task Handle(string json, string userName) { var request = JsonConvert.DeserializeObject(json); - var postSessionResponse = await repository.PostSession(new PostSession + var sessionName = await repository.PostSession(new PostSession { SessionName = request.GameName, PlayerName = userName, @@ -41,12 +38,12 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers PlayerName = userName, Game = new Game { - GameName = postSessionResponse.SessionName, + GameName = sessionName, Players = new[] { userName } } }; - if (string.IsNullOrWhiteSpace(postSessionResponse.SessionName)) + if (string.IsNullOrWhiteSpace(sessionName)) { response.Error = "Game already exists."; } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs index f8edb5e..6f22279 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs @@ -2,7 +2,6 @@ using Gameboard.ShogiUI.Sockets.Repositories; using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages; using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; -using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Threading.Tasks; @@ -10,16 +9,13 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers { public class JoinByCodeHandler : IActionHandler { - private readonly ILogger logger; private readonly IGameboardRepository repository; private readonly ISocketCommunicationManager communicationManager; public JoinByCodeHandler( - ILogger logger, ISocketCommunicationManager communicationManager, IGameboardRepository repository) { - this.logger = logger; this.repository = repository; this.communicationManager = communicationManager; } @@ -27,38 +23,38 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers public async Task Handle(string json, string userName) { var request = JsonConvert.DeserializeObject(json); - var joinGameResponse = await repository.PostJoinPrivateSession(new PostJoinPrivateSession + var sessionName = await repository.PostJoinPrivateSession(new PostJoinPrivateSession { PlayerName = userName, JoinCode = request.JoinCode }); - if (joinGameResponse.JoinSucceeded) + if (sessionName == null) { - // Other members of the game see a regular JoinGame occur. - var response = new JoinGameResponse(ClientAction.JoinGame) + var response = new JoinGameResponse(ClientAction.JoinByCode) { PlayerName = userName, - GameName = joinGameResponse.SessionName - }; - // At this time, userName hasn't subscribed and won't receive this message. - await communicationManager.BroadcastToGame(joinGameResponse.SessionName, response); - - // The player joining sees the JoinByCode occur. - response = new JoinGameResponse(ClientAction.JoinByCode) - { - PlayerName = userName, - GameName = joinGameResponse.SessionName + GameName = sessionName, + Error = "Error joining game." }; await communicationManager.BroadcastToPlayers(response, userName); } else { - var response = new JoinGameResponse(ClientAction.JoinByCode) + // Other members of the game see a regular JoinGame occur. + var response = new JoinGameResponse(ClientAction.JoinGame) { PlayerName = userName, - GameName = joinGameResponse.SessionName, - Error = "Error joining game." + GameName = sessionName + }; + // At this time, userName hasn't subscribed and won't receive this message. + await communicationManager.BroadcastToGame(sessionName, response); + + // The player joining sees the JoinByCode occur. + response = new JoinGameResponse(ClientAction.JoinByCode) + { + PlayerName = userName, + GameName = sessionName }; await communicationManager.BroadcastToPlayers(response, userName); } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs index c00aa64..150a137 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs @@ -23,7 +23,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers { var request = JsonConvert.DeserializeObject(json); - var joinGameResponse = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession + var joinSucceeded = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession { PlayerName = userName, SessionName = request.GameName @@ -34,7 +34,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers PlayerName = userName, GameName = request.GameName }; - if (joinGameResponse.JoinSucceeded) + if (joinSucceeded) { await communicationManager.BroadcastToAll(response); } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs index db15bfa..9efc43b 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs @@ -1,4 +1,4 @@ -using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.Rules; using Gameboard.ShogiUI.Sockets.Repositories; using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages; using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; @@ -37,9 +37,8 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers var gameTask = gameboardRepository.GetGame(request.GameName); var moveTask = gameboardRepository.GetMoves(request.GameName); - var getGameResponse = await gameTask; - var getMovesResponse = await moveTask; - if (getGameResponse == null || getMovesResponse == null) + var sessionModel = await gameTask; + if (sessionModel == null) { logger.LogWarning("{action} - {user} was unable to load session named {session}.", ClientAction.LoadGame, userName, request.GameName); var response = new LoadGameResponse(ClientAction.LoadGame) { Error = "Game not found." }; @@ -47,17 +46,17 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers } else { - var sessionModel = new Models.Session(getGameResponse.Session); - var moveModels = getMovesResponse.Moves.Select(_ => new Models.Move(_)).ToList(); + var moveModels = await moveTask; communicationManager.SubscribeToGame(sessionModel, userName); var boardMoves = moveModels.Select(_ => _.ToBoardModel()).ToList(); - boardManager.Add(getGameResponse.Session.Name, new ShogiBoard(boardMoves)); + var shogiBoard = new ShogiBoard(boardMoves); + boardManager.Add(sessionModel.Name, shogiBoard); var response = new LoadGameResponse(ClientAction.LoadGame) { Game = sessionModel.ToServiceModel(), - Moves = moveModels.Select(_ => _.ToServiceModel()).ToList(), + BoardState = new Models.BoardState(shogiBoard).ToServiceModel() }; await communicationManager.BroadcastToPlayers(response, userName); } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs index 5fe8a11..3fa5bb5 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using System.Threading.Tasks; using Service = Gameboard.ShogiUI.Sockets.ServiceModels.Socket; + namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers { public class MoveHandler : IActionHandler @@ -25,31 +26,40 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers public async Task Handle(string json, string userName) { var request = JsonConvert.DeserializeObject(json); - // Basic move validation - if (request.Move.To.Equals(request.Move.From)) - { - var error = new Service.Messages.ErrorResponse(Service.Types.ClientAction.Move) - { - Error = "Error: moving piece from tile to the same tile." - }; - await communicationManager.BroadcastToPlayers(error, userName); - return; - } - var moveModel = new Move(request.Move); var board = boardManager.Get(request.GameName); - var boardMove = moveModel.ToBoardModel(); - //board.Move() - await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel())); - - - var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move) + if (board == null) { - GameName = request.GameName, - PlayerName = userName, - Move = moveModel.ToServiceModel() - }; - await communicationManager.BroadcastToGame(request.GameName, response); + // TODO: Find a flow for this + var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move) + { + Error = $"Game isn't loaded. Send a message with the {Service.Types.ClientAction.LoadGame} action first." + }; + await communicationManager.BroadcastToPlayers(response, userName); + + } + var boardMove = moveModel.ToBoardModel(); + var moveSuccess = board.Move(boardMove); + if (moveSuccess) + { + await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel())); + var boardState = new BoardState(board); + var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move) + { + GameName = request.GameName, + PlayerName = userName, + BoardState = boardState.ToServiceModel() + }; + await communicationManager.BroadcastToGame(request.GameName, response); + } + else + { + var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move) + { + Error = "Invalid move." + }; + await communicationManager.BroadcastToPlayers(response, userName); + } } } } diff --git a/Gameboard.ShogiUI.Sockets/Models/BoardState.cs b/Gameboard.ShogiUI.Sockets/Models/BoardState.cs new file mode 100644 index 0000000..1d50469 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Models/BoardState.cs @@ -0,0 +1,40 @@ +using Gameboard.ShogiUI.Rules; +using System.Collections.Generic; +using System.Linq; +using ServiceTypes = Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; + +namespace Gameboard.ShogiUI.Sockets.Models +{ + public class BoardState + { + public Piece[,] Board { get; set; } + public IReadOnlyCollection Player1Hand { get; set; } + public IReadOnlyCollection Player2Hand { get; set; } + + public BoardState(ShogiBoard shogi) + { + Board = new Piece[9, 9]; + for (var x = 0; x < 9; x++) + for (var y = 0; y < 9; y++) + Board[x, y] = new Piece(shogi.Board[x, y]); + + Player1Hand = shogi.Hands[WhichPlayer.Player1].Select(_ => new Piece(_)).ToList(); + Player2Hand = shogi.Hands[WhichPlayer.Player2].Select(_ => new Piece(_)).ToList(); + } + + public ServiceTypes.BoardState ToServiceModel() + { + var board = new ServiceTypes.Piece[9, 9]; + Board = new Piece[9, 9]; + for (var x = 0; x < 9; x++) + for (var y = 0; y < 9; y++) + board[x, y] = Board[x, y].ToServiceModel(); + return new ServiceTypes.BoardState + { + Board = board, + Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(), + Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList() + }; + } + } +} diff --git a/Gameboard.ShogiUI.Sockets/Models/Move.cs b/Gameboard.ShogiUI.Sockets/Models/Move.cs index 66fda38..a597493 100644 --- a/Gameboard.ShogiUI.Sockets/Models/Move.cs +++ b/Gameboard.ShogiUI.Sockets/Models/Move.cs @@ -1,7 +1,8 @@ -using Gameboard.ShogiUI.BoardState; +using Gameboard.ShogiUI.Rules; using Microsoft.FSharp.Core; using System; using System.Numerics; +using BoardStateMove = Gameboard.ShogiUI.Rules.Move; using ShogiApi = Gameboard.Shogi.Api.ServiceModels.Types; namespace Gameboard.ShogiUI.Sockets.Models @@ -13,7 +14,6 @@ namespace Gameboard.ShogiUI.Sockets.Models public Coords To { get; set; } public bool IsPromotion { get; set; } - public Move() { } public Move(ServiceModels.Socket.Types.Move move) { From = Coords.FromBoardNotation(move.From); @@ -74,9 +74,9 @@ namespace Gameboard.ShogiUI.Sockets.Models }; return target; } - public BoardState.Move ToBoardModel() + public BoardStateMove ToBoardModel() { - return new BoardState.Move + return new BoardStateMove { From = new Vector2(From.X, From.Y), IsPromotion = IsPromotion, diff --git a/Gameboard.ShogiUI.Sockets/Models/Piece.cs b/Gameboard.ShogiUI.Sockets/Models/Piece.cs new file mode 100644 index 0000000..8ce5124 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Models/Piece.cs @@ -0,0 +1,27 @@ +using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; +using BoardStatePiece = Gameboard.ShogiUI.Rules.Pieces.Piece; + +namespace Gameboard.ShogiUI.Sockets.Models +{ + public class Piece + { + public WhichPiece WhichPiece { get; set; } + + public bool IsPromoted { get; set; } + + public Piece(BoardStatePiece piece) + { + WhichPiece = (WhichPiece)piece.WhichPiece; + IsPromoted = piece.IsPromoted; + } + + public ServiceModels.Socket.Types.Piece ToServiceModel() + { + return new ServiceModels.Socket.Types.Piece + { + IsPromoted = IsPromoted, + WhichPiece = WhichPiece + }; + } + } +} diff --git a/Gameboard.ShogiUI.Sockets/Models/Player.cs b/Gameboard.ShogiUI.Sockets/Models/Player.cs new file mode 100644 index 0000000..90fa28e --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Models/Player.cs @@ -0,0 +1,12 @@ +namespace Gameboard.ShogiUI.Sockets.Models +{ + public class Player + { + public string Name { get; } + + public Player(string name) + { + Name = name; + } + } +} diff --git a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json index 20f6c84..4a087fc 100644 --- a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json +++ b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json @@ -16,7 +16,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "AspShogiSockets": { + "Kestrel": { "commandName": "Project", "launchUrl": "Socket/Token", "environmentVariables": { diff --git a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs index 25583c9..bd483eb 100644 --- a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs +++ b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs @@ -1,7 +1,10 @@ using Gameboard.Shogi.Api.ServiceModels.Messages; +using Gameboard.ShogiUI.Sockets.Models; using Gameboard.ShogiUI.Sockets.Repositories.Utility; using Newtonsoft.Json; using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -11,17 +14,17 @@ namespace Gameboard.ShogiUI.Sockets.Repositories public interface IGameboardRepository { Task DeleteGame(string gameName); - Task GetGame(string gameName); + Task GetGame(string gameName); Task GetGames(); Task GetGames(string playerName); - Task GetMoves(string gameName); - Task PostSession(PostSession request); - Task PostJoinPrivateSession(PostJoinPrivateSession request); - Task PutJoinPublicSession(PutJoinPublicSession request); + Task> GetMoves(string gameName); + Task PostSession(PostSession request); + Task PostJoinPrivateSession(PostJoinPrivateSession request); + Task PutJoinPublicSession(PutJoinPublicSession request); Task PostMove(string gameName, PostMove request); - Task PostJoinCode(string gameName, string userName); - Task GetPlayer(string userName); - Task PostPlayer(PostPlayer request); + Task PostJoinCode(string gameName, string userName); + Task GetPlayer(string userName); + Task PostPlayer(PostPlayer request); } public class GameboardRepository : IGameboardRepository @@ -52,12 +55,16 @@ namespace Gameboard.ShogiUI.Sockets.Repositories return JsonConvert.DeserializeObject(json); } - public async Task GetGame(string gameName) + public async Task GetGame(string gameName) { var uri = $"Session/{gameName}"; var response = await client.GetAsync(Uri.EscapeUriString(uri)); var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + if (string.IsNullOrWhiteSpace(json)) + { + return null; + } + return new Session(JsonConvert.DeserializeObject(json).Session); } public async Task DeleteGame(string gameName) @@ -66,36 +73,46 @@ namespace Gameboard.ShogiUI.Sockets.Repositories await client.DeleteAsync(Uri.EscapeUriString(uri)); } - public async Task PostSession(PostSession request) + public async Task PostSession(PostSession request) { var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType); var response = await client.PostAsync(PostSessionRoute, content); var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + return JsonConvert.DeserializeObject(json).SessionName; } - public async Task PutJoinPublicSession(PutJoinPublicSession request) + public async Task PutJoinPublicSession(PutJoinPublicSession request) { var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType); var response = await client.PutAsync(JoinSessionRoute, content); var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + return JsonConvert.DeserializeObject(json).JoinSucceeded; } - public async Task PostJoinPrivateSession(PostJoinPrivateSession request) + public async Task PostJoinPrivateSession(PostJoinPrivateSession request) { var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType); var response = await client.PostAsync(JoinSessionRoute, content); var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + var deserialized = JsonConvert.DeserializeObject(json); + if (deserialized.JoinSucceeded) + { + return deserialized.SessionName; + } + return null; } - public async Task GetMoves(string gameName) + public async Task> GetMoves(string gameName) { var uri = $"Session/{gameName}/Moves"; - var response = await client.GetAsync(Uri.EscapeUriString(uri)); - var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + var get = await client.GetAsync(Uri.EscapeUriString(uri)); + var json = await get.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(json)) + { + return new List(); + } + var response = JsonConvert.DeserializeObject(json); + return response.Moves.Select(m => new Move(m)).ToList(); } public async Task PostMove(string gameName, PostMove request) @@ -105,27 +122,33 @@ namespace Gameboard.ShogiUI.Sockets.Repositories await client.PostAsync(Uri.EscapeUriString(uri), content); } - public async Task PostJoinCode(string gameName, string userName) + public async Task PostJoinCode(string gameName, string userName) { var uri = $"JoinCode/{gameName}"; var serialized = JsonConvert.SerializeObject(new PostJoinCode { PlayerName = userName }); var content = new StringContent(serialized, Encoding.UTF8, MediaType); var json = await (await client.PostAsync(Uri.EscapeUriString(uri), content)).Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + return JsonConvert.DeserializeObject(json).JoinCode; } - public async Task GetPlayer(string playerName) + public async Task GetPlayer(string playerName) { var uri = $"Player/{playerName}"; - var response = await client.GetAsync(Uri.EscapeUriString(uri)); - var json = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + var get = await client.GetAsync(Uri.EscapeUriString(uri)); + var content = await get.Content.ReadAsStringAsync(); + if (!string.IsNullOrWhiteSpace(content)) + { + var response = JsonConvert.DeserializeObject(content); + return new Player(response.Player.Name); + } + return null; } - public async Task PostPlayer(PostPlayer request) + public async Task PostPlayer(PostPlayer request) { var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType); - return await client.PostAsync(PlayerRoute, content); + var response = await client.PostAsync(PlayerRoute, content); + return response.IsSuccessStatusCode; } } } diff --git a/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs b/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs index 55ff15a..30fed61 100644 --- a/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs +++ b/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs @@ -9,6 +9,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers Task CreateGuestUser(); Task IsPlayer1(string sessionName, string playerName); bool IsGuest(string playerName); + Task PlayerExists(string playerName); } public class GameboardRepositoryManager : IGameboardRepositoryManager @@ -33,8 +34,8 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers { PlayerName = clientId }; - var response = await repository.PostPlayer(request); - if (response.IsSuccessStatusCode) + var isCreated = await repository.PostPlayer(request); + if (isCreated) { return clientId; } @@ -45,19 +46,21 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers public async Task IsPlayer1(string sessionName, string playerName) { var session = await repository.GetGame(sessionName); - return session?.Session.Player1 == playerName; + return session?.Player1 == playerName; } public async Task CreateJoinCode(string sessionName, string playerName) { - var getGameResponse = await repository.GetGame(sessionName); - if (playerName == getGameResponse?.Session.Player1) + var session = await repository.GetGame(sessionName); + if (playerName == session?.Player1) { - return (await repository.PostJoinCode(sessionName, playerName)).JoinCode; + return await repository.PostJoinCode(sessionName, playerName); } return null; } public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix); + + public async Task PlayerExists(string playerName) => await repository.GetPlayer(playerName) != null; } } diff --git a/Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs b/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs similarity index 91% rename from Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs rename to Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs index 9eac527..7145e5e 100644 --- a/Gameboard.ShogiUI.UnitTests/BoardState/BoardStateExtensions.cs +++ b/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs @@ -1,10 +1,10 @@ -using Gameboard.ShogiUI.BoardState; -using Gameboard.ShogiUI.BoardState.Pieces; +using Gameboard.ShogiUI.Rules; +using Gameboard.ShogiUI.Rules.Pieces; using System; using System.Text; using System.Text.RegularExpressions; -namespace Gameboard.ShogiUI.UnitTests.BoardState +namespace Gameboard.ShogiUI.UnitTests.Rules { public static class BoardStateExtensions { @@ -32,7 +32,7 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var builder = new StringBuilder(); builder.Append(" Player 2(.)"); builder.AppendLine(); - for (var y = 8; y > -1; y--) + for (var y = 0; y < 9; y++) { builder.Append("- "); for (var x = 0; x < 8; x++) builder.Append("- - "); diff --git a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs similarity index 74% rename from Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs rename to Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs index 1178c55..fbe26b3 100644 --- a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs +++ b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs @@ -1,12 +1,12 @@ using FluentAssertions; -using Gameboard.ShogiUI.BoardState; -using Gameboard.ShogiUI.BoardState.Pieces; +using Gameboard.ShogiUI.Rules; +using Gameboard.ShogiUI.Rules.Pieces; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; using System.Numerics; -namespace Gameboard.ShogiUI.UnitTests.BoardState +namespace Gameboard.ShogiUI.UnitTests.Rules { [TestClass] public class ShogiBoardShould @@ -22,7 +22,7 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState // Assert Player1. for (var y = 0; y < 3; y++) for (var x = 0; x < 9; x++) - board[x, y]?.Owner.Should().Be(WhichPlayer.Player1); + board[x, y]?.Owner.Should().Be(WhichPlayer.Player2); board[0, 0].WhichPiece.Should().Be(WhichPiece.Lance); board[1, 0].WhichPiece.Should().Be(WhichPiece.Knight); board[2, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral); @@ -33,9 +33,9 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState board[7, 0].WhichPiece.Should().Be(WhichPiece.Knight); board[8, 0].WhichPiece.Should().Be(WhichPiece.Lance); board[0, 1].Should().BeNull(); - board[1, 1].WhichPiece.Should().Be(WhichPiece.Bishop); + board[1, 1].WhichPiece.Should().Be(WhichPiece.Rook); for (var x = 2; x < 7; x++) board[x, 1].Should().BeNull(); - board[7, 1].WhichPiece.Should().Be(WhichPiece.Rook); + board[7, 1].WhichPiece.Should().Be(WhichPiece.Bishop); board[8, 1].Should().BeNull(); for (var x = 0; x < 9; x++) board[x, 2].WhichPiece.Should().Be(WhichPiece.Pawn); @@ -47,7 +47,7 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState // Assert Player2. for (var y = 6; y < 9; y++) for (var x = 0; x < 9; x++) - board[x, y]?.Owner.Should().Be(WhichPlayer.Player2); + board[x, y]?.Owner.Should().Be(WhichPlayer.Player1); board[0, 8].WhichPiece.Should().Be(WhichPiece.Lance); board[1, 8].WhichPiece.Should().Be(WhichPiece.Knight); board[2, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral); @@ -58,9 +58,9 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState board[7, 8].WhichPiece.Should().Be(WhichPiece.Knight); board[8, 8].WhichPiece.Should().Be(WhichPiece.Lance); board[0, 7].Should().BeNull(); - board[1, 7].WhichPiece.Should().Be(WhichPiece.Rook); + board[1, 7].WhichPiece.Should().Be(WhichPiece.Bishop); for (var x = 2; x < 7; x++) board[x, 7].Should().BeNull(); - board[7, 7].WhichPiece.Should().Be(WhichPiece.Bishop); + board[7, 7].WhichPiece.Should().Be(WhichPiece.Rook); board[8, 7].Should().BeNull(); for (var x = 0; x < 9; x++) board[x, 6].WhichPiece.Should().Be(WhichPiece.Pawn); } @@ -73,13 +73,13 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState new Move { // Pawn - From = new Vector2(0, 2), - To = new Vector2(0, 3) + From = new Vector2(0, 6), + To = new Vector2(0, 5) } }; var shogi = new ShogiBoard(moves); - shogi.Board[0, 2].Should().BeNull(); - shogi.Board[0, 3].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Board[0, 6].Should().BeNull(); + shogi.Board[0, 5].WhichPiece.Should().Be(WhichPiece.Pawn); } [TestMethod] @@ -106,11 +106,11 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var shogi = new ShogiBoard(); // Act - P1 "moves" pawn to the position it already exists at. - var moveSuccess = shogi.Move(new Move { From = new Vector2(0, 2), To = new Vector2(0, 2) }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(0, 6), To = new Vector2(0, 6) }); // Assert moveSuccess.Should().BeFalse(); - shogi.Board[0, 2].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Board[0, 6].WhichPiece.Should().Be(WhichPiece.Pawn); } [TestMethod] @@ -136,9 +136,11 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState { // Arrange var shogi = new ShogiBoard(); + shogi.WhoseTurn.Should().Be(WhichPlayer.Player1); + shogi.Board[8, 2].Owner.Should().Be(WhichPlayer.Player2); // Act - Move Player2 Pawn when it's Player1 turn. - var moveSuccess = shogi.Move(new Move { From = new Vector2(8, 6), To = new Vector2(8, 5) }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(8, 2), To = new Vector2(8, 3) }); // Assert moveSuccess.Should().BeFalse(); @@ -152,8 +154,8 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var invalidLanceMove = new Move { // Lance moving through the pawn before it. - From = new Vector2(0, 0), - To = new Vector2(0, 5) + From = new Vector2(0, 8), + To = new Vector2(0, 4) }; var shogi = new ShogiBoard(); @@ -170,8 +172,8 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var invalidKnightMove = new Move { // Knight capturing allied Pawn - From = new Vector2(1, 0), - To = new Vector2(0, 2) + From = new Vector2(1, 8), + To = new Vector2(0, 6) }; var shogi = new ShogiBoard(); @@ -190,11 +192,11 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) }, + new Move { From = new Vector2(6, 2), To = new Vector2(6, 3) }, // P1 Bishop puts P2 in check - new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) } + new Move { From = new Vector2(1, 7), To = new Vector2(6, 2) } }; var shogi = new ShogiBoard(moves); @@ -202,7 +204,7 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState shogi.InCheck.Should().Be(WhichPlayer.Player2); // Act - P2 moves Lance while remaining in check. - var moveSuccess = shogi.Move(new Move { From = new Vector2(8, 8), To = new Vector2(8, 7) }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(0, 8), To = new Vector2(0, 7) }); // Assert moveSuccess.Should().BeFalse(); @@ -218,61 +220,62 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(0, 6), To = new Vector2(0, 5) }, + new Move { From = new Vector2(0, 2), To = new Vector2(0, 3) }, // P1 Bishop takes P2 Pawn - new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }, + new Move { From = new Vector2(1, 7), To = new Vector2(6, 2) }, // P2 Gold, block check from P1 Bishop. - new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) }, + new Move { From = new Vector2(5, 0), To = new Vector2(5, 1) }, // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance - new Move { From = new Vector2(6, 6), To = new Vector2(7, 7), IsPromotion = true }, + new Move { From = new Vector2(6, 2), To = new Vector2(7, 1), IsPromotion = true }, // P2 Pawn again - new Move { From = new Vector2(0, 5), To = new Vector2(0, 4) }, + new Move { From = new Vector2(0, 3), To = new Vector2(0, 4) }, // P1 Bishop takes P2 Knight - new Move { From = new Vector2(7, 7), To = new Vector2(7, 8) }, + new Move { From = new Vector2(7, 1), To = new Vector2(7, 0) }, // P2 Pawn again - new Move { From = new Vector2(0, 4), To = new Vector2(0, 3) }, + new Move { From = new Vector2(0, 4), To = new Vector2(0, 5) }, // P1 Bishop takes P2 Lance - new Move { From = new Vector2(7, 8), To = new Vector2(8, 8) }, + new Move { From = new Vector2(7, 0), To = new Vector2(8, 0) }, // P2 Lance (move to make room for attempted P1 Pawn placement) - new Move { From = new Vector2(0, 8), To = new Vector2(0, 7) }, + new Move { From = new Vector2(0, 0), To = new Vector2(0, 1) }, // P1 arbitrary move - new Move { From = new Vector2(4, 0), To = new Vector2(4, 1) }, + new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) }, // P2 Pawn again, takes P1 Pawn - new Move { From = new Vector2(0, 3), To = new Vector2(0, 2) }, + new Move { From = new Vector2(0, 5), To = new Vector2(0, 6) }, }; var shogi = new ShogiBoard(moves); - shogi.PrintStateAsAscii(); // Prerequisites + shogi.Hands[WhichPlayer.Player1].Count.Should().Be(4); 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); + shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); // Act | Assert - It is P1 turn /// try illegally placing Knight from the hand. - shogi.Board[7, 8].Should().BeNull(); - var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 8) }); + shogi.Board[7, 0].Should().BeNull(); + var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 0) }); 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) }); + shogi.Board[7, 0].Should().BeNull(); + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 1) }); dropSuccess.Should().BeFalse(); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - shogi.Board[7, 7].Should().BeNull(); + shogi.Board[7, 1].Should().BeNull(); /// try illegally placing Pawn from the hand - dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Pawn, To = new Vector2(7, 8) }); + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Pawn, To = new Vector2(7, 0) }); dropSuccess.Should().BeFalse(); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - shogi.Board[7, 8].Should().BeNull(); + shogi.Board[7, 0].Should().BeNull(); /// try illegally placing Lance from the hand - dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(7, 8) }); + dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(7, 0) }); dropSuccess.Should().BeFalse(); shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - shogi.Board[7, 8].Should().BeNull(); + shogi.Board[7, 0].Should().BeNull(); } [TestMethod] @@ -282,25 +285,25 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(8, 6), To = new Vector2(8, 5) }, + new Move { From = new Vector2(8, 2), To = new Vector2(8, 3) }, // P1 Bishop, check - new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }, + new Move { From = new Vector2(1, 7), To = new Vector2(6, 2) }, // P2 Gold, block check - new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) }, + new Move { From = new Vector2(5, 0), To = new Vector2(5, 1) }, // P1 arbitrary move - new Move { From = new Vector2(0, 2), To = new Vector2(0, 3) }, + new Move { From = new Vector2(0, 6), To = new Vector2(0, 5) }, // P2 Bishop - new Move { From = new Vector2(7, 7), To = new Vector2(8, 6) }, + new Move { From = new Vector2(7, 1), To = new Vector2(8, 2) }, // P1 Bishop takes P2 Lance - new Move { From = new Vector2(6, 6), To = new Vector2(8, 8) }, + new Move { From = new Vector2(6, 2), To = new Vector2(8, 0) }, // P2 Bishop - new Move { From = new Vector2(8, 6), To = new Vector2(7, 7) }, + new Move { From = new Vector2(8, 2), To = new Vector2(7, 1) }, // P1 arbitrary move - new Move { From = new Vector2(0, 3), To = new Vector2(0, 4) }, + new Move { From = new Vector2(0, 5), To = new Vector2(0, 4) }, // P2 Bishop, check - new Move { From = new Vector2(7, 7), To = new Vector2(2, 2) }, + new Move { From = new Vector2(7, 1), To = new Vector2(2, 6) }, }; var shogi = new ShogiBoard(moves); @@ -325,22 +328,23 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) }, + new Move { From = new Vector2(6, 2), To = new Vector2(6, 3) }, // P1 Bishop, capture P2 Pawn, check - new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }, + new Move { From = new Vector2(1, 7), To = new Vector2(6, 2) }, // P2 Gold, block check - new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) }, + new Move { From = new Vector2(5, 0), To = new Vector2(5, 1) }, // P1 Bishop capture P2 Bishop - new Move { From = new Vector2(6, 6), To = new Vector2(7, 7) }, + new Move { From = new Vector2(6, 2), To = new Vector2(7, 1) }, // P2 arbitrary move - new Move { From = new Vector2(0, 8), To = new Vector2(0, 7) }, + new Move { From = new Vector2(0, 0), To = new Vector2(0, 1) }, }; var shogi = new ShogiBoard(moves); // Prerequisites shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.Board[4, 0].Should().NotBeNull(); // 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) }); @@ -358,16 +362,14 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) }, + new Move { From = new Vector2(6, 2), To = new Vector2(6, 3) }, }; var shogi = new ShogiBoard(moves); - shogi.PrintStateAsAscii(); - // Act - P1 Bishop, check - shogi.Move(new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) }); + shogi.Move(new Move { From = new Vector2(1, 7), To = new Vector2(6, 2) }); // Assert shogi.InCheck.Should().Be(WhichPlayer.Player2); @@ -380,14 +382,14 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) } + new Move { From = new Vector2(6, 2), To = new Vector2(6, 3) } }; var shogi = new ShogiBoard(moves); // Act - P1 Bishop captures P2 Bishop - var moveSuccess = shogi.Move(new Move { From = new Vector2(1, 1), To = new Vector2(7, 7) }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(1, 7), To = new Vector2(7, 1) }); // Assert moveSuccess.Should().BeTrue(); @@ -396,20 +398,20 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState .Count(piece => piece?.WhichPiece == WhichPiece.Bishop) .Should() .Be(1); - shogi.Board[1, 1].Should().BeNull(); - shogi.Board[7, 7].WhichPiece.Should().Be(WhichPiece.Bishop); + shogi.Board[1, 7].Should().BeNull(); + shogi.Board[7, 1].WhichPiece.Should().Be(WhichPiece.Bishop); shogi.Hands[WhichPlayer.Player1] .Should() .ContainSingle(piece => piece.WhichPiece == WhichPiece.Bishop && piece.Owner == WhichPlayer.Player1); // Act - P2 Silver captures P1 Bishop - moveSuccess = shogi.Move(new Move { From = new Vector2(6, 8), To = new Vector2(7, 7) }); + moveSuccess = shogi.Move(new Move { From = new Vector2(6, 0), To = new Vector2(7, 1) }); // Assert moveSuccess.Should().BeTrue(); - shogi.Board[6, 8].Should().BeNull(); - shogi.Board[7, 7].WhichPiece.Should().Be(WhichPiece.SilverGeneral); + shogi.Board[6, 0].Should().BeNull(); + shogi.Board[7, 1].WhichPiece.Should().Be(WhichPiece.SilverGeneral); shogi.Board .Cast() .Count(piece => piece?.WhichPiece == WhichPiece.Bishop) @@ -426,19 +428,19 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Pawn - new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) }, + new Move { From = new Vector2(2, 6), To = new Vector2(2, 5) }, // P2 Pawn - new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) } + new Move { From = new Vector2(6, 2), To = new Vector2(6, 3) } }; var shogi = new ShogiBoard(moves); // Act - P1 moves across promote threshold. - var moveSuccess = shogi.Move(new Move { From = new Vector2(1, 1), To = new Vector2(6, 6), IsPromotion = true }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(1, 7), To = new Vector2(6, 2), IsPromotion = true }); // Assert moveSuccess.Should().BeTrue(); - shogi.Board[1, 1].Should().BeNull(); - shogi.Board[6, 6].Should().Match(piece => piece.WhichPiece == WhichPiece.Bishop && piece.IsPromoted == true); + shogi.Board[1, 7].Should().BeNull(); + shogi.Board[6, 2].Should().Match(piece => piece.WhichPiece == WhichPiece.Bishop && piece.IsPromoted == true); } [TestMethod] @@ -448,32 +450,30 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState var moves = new[] { // P1 Rook - new Move { From = new Vector2(7, 1), To = new Vector2(4, 1) }, + new Move { From = new Vector2(7, 7), To = new Vector2(4, 7) }, // P2 Gold - new Move { From = new Vector2(3, 8), To = new Vector2(2, 7) }, + new Move { From = new Vector2(3, 0), To = new Vector2(2, 1) }, // P1 Pawn - new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) }, - // P2 other Gold - new Move { From = new Vector2(5, 8), To = new Vector2(6, 7) }, - // P1 same Pawn - new Move { From = new Vector2(4, 3), To = new Vector2(4, 4) }, - // P2 Pawn new Move { From = new Vector2(4, 6), To = new Vector2(4, 5) }, + // P2 other Gold + new Move { From = new Vector2(5, 0), To = new Vector2(6, 1) }, + // P1 same Pawn + new Move { From = new Vector2(4, 5), To = new Vector2(4, 4) }, + // P2 Pawn + new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) }, // P1 Pawn takes P2 Pawn - new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) }, + new Move { From = new Vector2(4, 4), To = new Vector2(4, 3) }, // P2 King - new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) }, + new Move { From = new Vector2(4, 0), To = new Vector2(4, 1) }, // P1 Pawn promotes, threatens P2 King - new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true }, + new Move { From = new Vector2(4, 3), To = new Vector2(4, 2), IsPromotion = true }, // P2 King retreat - new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) }, + new Move { From = new Vector2(4, 1), To = new Vector2(4, 0) }, }; var shogi = new ShogiBoard(moves); - Console.WriteLine("Prereq"); - shogi.PrintStateAsAscii(); // Act - P1 Pawn wins by checkmate. - var moveSuccess = shogi.Move(new Move { From = new Vector2(4, 6), To = new Vector2(4, 7) }); + var moveSuccess = shogi.Move(new Move { From = new Vector2(4, 2), To = new Vector2(4, 1) }); // Assert - checkmate moveSuccess.Should().BeTrue(); diff --git a/PathFinding/Direction.cs b/PathFinding/Direction.cs index 2ee825d..8a199e4 100644 --- a/PathFinding/Direction.cs +++ b/PathFinding/Direction.cs @@ -4,15 +4,15 @@ 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 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); + 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/PathFinder2D.cs b/PathFinding/PathFinder2D.cs index ae32b6b..f2c9323 100644 --- a/PathFinding/PathFinder2D.cs +++ b/PathFinding/PathFinder2D.cs @@ -6,8 +6,6 @@ namespace PathFinding { public class PathFinder2D where T : IPlanarElement { - /// - /// /// Guaranteed to be non-null. /// public delegate void Callback(T collider, Vector2 position); @@ -108,14 +106,8 @@ namespace PathFinding 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 static bool IsPathable(Vector2 origin, Vector2 destination, T element) - { - var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination); - return IsPathable(origin, destination, path.Direction); - } public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction) { - direction = Vector2.Normalize(direction); var next = Vector2.Add(origin, direction); if (Vector2.Distance(next, destination) >= Vector2.Distance(origin, destination)) return false;