diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Coords.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Coords.cs deleted file mode 100644 index d64dae4..0000000 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Coords.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types -{ - public class Coords - { - public int X { get; set; } - public int Y { get; set; } - } -} diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs index f815b00..3ad3719 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs @@ -1,33 +1,12 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types { - public class Move - { - public string PieceFromCaptured { get; set; } - public Coords From { get; set; } - public Coords To { get; set; } - public bool IsPromotion { get; set; } - - /// - /// Toggles perspective of this move. (ie from player 1 to player 2) - /// - public static Move ConvertPerspective(Move m) - { - var convertedMove = new Move - { - To = new Coords - { - X = 8 - m.To.X, - Y = 8 - m.To.Y - }, - From = new Coords - { - X = 8 - m.From.X, - Y = 8 - m.From.Y - }, - IsPromotion = m.IsPromotion, - PieceFromCaptured = m.PieceFromCaptured - }; - return convertedMove; - } - } + public class Move + { + public string PieceFromCaptured { get; set; } + /// Board position notation, like A3 or G1 + public string From { get; set; } + /// Board position notation, like A3 or G1 + public string To { get; set; } + public bool IsPromotion { get; set; } + } } diff --git a/Gameboard.ShogiUI.Sockets.UnitTests/Gameboard.ShogiUI.Sockets.UnitTests.csproj b/Gameboard.ShogiUI.Sockets.UnitTests/Gameboard.ShogiUI.Sockets.UnitTests.csproj new file mode 100644 index 0000000..02bde77 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets.UnitTests/Gameboard.ShogiUI.Sockets.UnitTests.csproj @@ -0,0 +1,19 @@ + + + + net5.0 + + + + + + + + + + + + + + + diff --git a/Gameboard.ShogiUI.Sockets.UnitTests/Models/CoordsModelShould.cs b/Gameboard.ShogiUI.Sockets.UnitTests/Models/CoordsModelShould.cs new file mode 100644 index 0000000..d0c1961 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets.UnitTests/Models/CoordsModelShould.cs @@ -0,0 +1,27 @@ +using FluentAssertions; +using Gameboard.ShogiUI.Sockets.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Gameboard.ShogiUI.Sockets.UnitTests.Models +{ + [TestClass] + public class CoordsModelShould + { + [TestMethod] + public void ConvertToNotation() + { + var letters = "ABCDEFGHI"; + + for (var x = 0; x < 8; x++) // file + { + for (var y = 0; y < 8; y++) // rank + { + var move = new Coords(x, y); + var actual = move.ToBoardNotation(); + var expected = $"{letters[x]}{y + 1}"; + actual.Should().Be(expected); + } + } + } + } +} diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln index 85cdd75..adfe2f0 100644 --- a/Gameboard.ShogiUI.Sockets.sln +++ b/Gameboard.ShogiUI.Sockets.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets.ServiceModels", "Gameboard.ShogiUI.Sockets.ServiceModels\Gameboard.ShogiUI.Sockets.ServiceModels.csproj", "{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gameboard.ShogiUI.Sockets.UnitTests", "Gameboard.ShogiUI.Sockets.UnitTests\Gameboard.ShogiUI.Sockets.UnitTests.csproj", "{8D753AD0-0985-415C-80B3-CCADF3AE1DF9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,10 +25,17 @@ Global {FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.Build.0 = Release|Any CPU + {8D753AD0-0985-415C-80B3-CCADF3AE1DF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D753AD0-0985-415C-80B3-CCADF3AE1DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D753AD0-0985-415C-80B3-CCADF3AE1DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D753AD0-0985-415C-80B3-CCADF3AE1DF9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8D753AD0-0985-415C-80B3-CCADF3AE1DF9} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB} EndGlobalSection diff --git a/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs new file mode 100644 index 0000000..49f21a4 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs @@ -0,0 +1,6 @@ +namespace Gameboard.ShogiUI.Sockets.Managers +{ + public class BoardManager + { + } +} diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs index 380e938..b0fac4f 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs @@ -41,13 +41,11 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers } else { - var session = new Session(getGameResponse.Session); - communicationManager.SubscribeToGame(socket, session, userName); + var sessionModel = new Session(getGameResponse.Session); + communicationManager.SubscribeToGame(socket, sessionModel, userName); - response.Game = session.ToServiceModel(); - response.Moves = userName.Equals(session.Player1) - ? getMovesResponse.Moves.Select(_ => Mapper.Map(_)) - : getMovesResponse.Moves.Select(_ => Move.ConvertPerspective(Mapper.Map(_))); + response.Game = sessionModel.ToServiceModel(); + response.Moves = getMovesResponse.Moves.Select(_ => Mapper.Map(_).ToServiceModel()); } var serialized = JsonConvert.SerializeObject(response); diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs index ee3cd05..fdc535b 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs @@ -2,11 +2,11 @@ using Gameboard.ShogiUI.Sockets.Extensions; using Gameboard.ShogiUI.Sockets.Managers.Utility; using Gameboard.ShogiUI.Sockets.Repositories; -using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages; -using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; +using Service = Gameboard.ShogiUI.Sockets.ServiceModels.Socket; using Newtonsoft.Json; using System.Net.WebSockets; using System.Threading.Tasks; +using Gameboard.ShogiUI.Sockets.Models; namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers { @@ -24,12 +24,12 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers public async Task Handle(WebSocket socket, string json, string userName) { - var request = JsonConvert.DeserializeObject(json); + var request = JsonConvert.DeserializeObject(json); // Basic move validation if (request.Move.To.Equals(request.Move.From)) { var serialized = JsonConvert.SerializeObject( - new ErrorResponse(ClientAction.Move) + new Service.Messages.ErrorResponse(Service.Types.ClientAction.Move) { Error = "Error: moving piece from tile to the same tile." }); @@ -37,25 +37,17 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers return; } + var moveModel = new Move(request.Move); var session = (await gameboardRepository.GetGame(request.GameName)).Session; - var isPlayer2 = userName == session.Player2; - // Shogi.Api expects the move coordinates from the perspective of player 1. - var move = isPlayer2 ? Move.ConvertPerspective(request.Move) : request.Move; - await gameboardRepository.PostMove(request.GameName, new PostMove(Mapper.Map(move))); + await gameboardRepository.PostMove(request.GameName, new PostMove(Mapper.Map(moveModel))); - var responseForPlayer1 = new MoveResponse(ClientAction.Move) + var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move) { GameName = request.GameName, PlayerName = userName, - Move = isPlayer2 ? Move.ConvertPerspective(request.Move) : request.Move + Move = moveModel.ToServiceModel() }; - var responseForPlayer2 = new MoveResponse(ClientAction.Move) - { - GameName = request.GameName, - PlayerName = userName, - Move = isPlayer2 ? request.Move : Move.ConvertPerspective(request.Move) - }; - await communicationManager.BroadcastToGame(session.Name, responseForPlayer1, responseForPlayer2); + await communicationManager.BroadcastToGame(session.Name, response); } } } diff --git a/Gameboard.ShogiUI.Sockets/Managers/Utility/Mapper.cs b/Gameboard.ShogiUI.Sockets/Managers/Utility/Mapper.cs index 7302fe5..ea59d0e 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/Utility/Mapper.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/Utility/Mapper.cs @@ -1,4 +1,4 @@ -using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types; +using Gameboard.ShogiUI.Sockets.Models; using Microsoft.FSharp.Core; using ShogiApi = Gameboard.Shogi.Api.ServiceModels.Types; @@ -55,8 +55,8 @@ namespace Gameboard.ShogiUI.Sockets.Managers.Utility var target = new Move { - From = new Coords { X = origin.X, Y = origin.Y }, - To = new Coords { X = destination.X, Y = destination.Y }, + From = new Coords(origin.X, origin.Y), + To = new Coords(destination.X, destination.Y), IsPromotion = source.IsPromotion, PieceFromCaptured = pieceFromCaptured }; diff --git a/Gameboard.ShogiUI.Sockets/Models/Coords.cs b/Gameboard.ShogiUI.Sockets/Models/Coords.cs new file mode 100644 index 0000000..2104cde --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Models/Coords.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.RegularExpressions; + +namespace Gameboard.ShogiUI.Sockets.Models +{ + public class Coords + { + private const string BoardNotationRegex = @"(?[A-I])(?[1-9])"; + private const char A = 'A'; + public int X { get; } + public int Y { get; } + public Coords(int x, int y) + { + X = x; + Y = y; + } + + public string ToBoardNotation() + { + var file = (char)(X + A); + var rank = Y + 1; + return $"{file}{rank}"; + } + + public static Coords FromBoardNotation(string notation) + { + if (string.IsNullOrEmpty(notation)) + { + if (Regex.IsMatch(notation, BoardNotationRegex)) + { + var match = Regex.Match(notation, BoardNotationRegex); + char file = match.Groups["file"].Value[0]; + int rank = int.Parse(match.Groups["rank"].Value); + return new Coords(file - A, rank); + } + throw new ArgumentException("Board notation not recognized."); // TODO: Move this error handling to the service layer. + } + return new Coords(-1, -1); // Temporarily this is how I tell Gameboard.API that a piece came from the hand. + } + } +} diff --git a/Gameboard.ShogiUI.Sockets/Models/Move.cs b/Gameboard.ShogiUI.Sockets/Models/Move.cs new file mode 100644 index 0000000..932285a --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Models/Move.cs @@ -0,0 +1,27 @@ +namespace Gameboard.ShogiUI.Sockets.Models +{ + public class Move + { + public string PieceFromCaptured { get; set; } + public Coords From { get; set; } + public Coords To { get; set; } + public bool IsPromotion { get; set; } + + public Move() { } + public Move(ServiceModels.Socket.Types.Move move) + { + From = Coords.FromBoardNotation(move.From); + To = Coords.FromBoardNotation(move.To); + PieceFromCaptured = move.PieceFromCaptured; + IsPromotion = move.IsPromotion; + } + + public ServiceModels.Socket.Types.Move ToServiceModel() => new ServiceModels.Socket.Types.Move + { + From = From.ToBoardNotation(), + IsPromotion = IsPromotion, + PieceFromCaptured = PieceFromCaptured, + To = To.ToBoardNotation() + }; + } +}