diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs index 5fa50d1..1a43c63 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs @@ -1,11 +1,11 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Types; -using System.Collections.ObjectModel; +using System.Collections.Generic; namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api { public class GetSessionsResponse { - public Collection PlayerHasJoinedSessions { get; set; } - public Collection AllOtherSessions { get; set; } + public IList PlayerHasJoinedSessions { get; set; } + public IList AllOtherSessions { get; set; } } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs index 71fdeca..306e3f1 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs @@ -1,12 +1,11 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Types; -using System; namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket { - public class CreateGameResponse : IResponse + public class CreateGameResponse : ISocketResponse { public string Action { get; } - public Session Game { get; set; } + public SessionMetadata Game { get; set; } /// /// The player who created the game. diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/IResponse.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/IResponse.cs index 69d18c6..f72caaa 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/IResponse.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/IResponse.cs @@ -1,6 +1,6 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket { - public interface IResponse + public interface ISocketResponse { string Action { get; } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/JoinGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/JoinGame.cs index 22fac9c..fc4f786 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/JoinGame.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/JoinGame.cs @@ -14,7 +14,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket public string GameName { get; set; } = ""; } - public class JoinGameResponse : IResponse + public class JoinGameResponse : ISocketResponse { public string Action { get; protected set; } public string GameName { get; set; } @@ -31,7 +31,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket } } - public class JoinByCodeResponse : JoinGameResponse, IResponse + public class JoinByCodeResponse : JoinGameResponse, ISocketResponse { public JoinByCodeResponse() { diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs index 0b19a44..f5a3fb1 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs @@ -2,7 +2,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket { - public class MoveResponse : IResponse + public class MoveResponse : ISocketResponse { public string Action { get; } public string GameName { get; set; } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/SessionMetadata.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/SessionMetadata.cs new file mode 100644 index 0000000..660ebe0 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/SessionMetadata.cs @@ -0,0 +1,12 @@ +namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types +{ + public class SessionMetadata + { + public string Name { get; set; } + public string Player1 { get; set; } + public string? Player2 { get; set; } + public bool IsPrivate { get; set; } + + public string[] Players => string.IsNullOrEmpty(Player2) ? new[] { Player1 } : new[] { Player1, Player2 }; + } +} diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln index 82bd209..5e1eeee 100644 --- a/Gameboard.ShogiUI.Sockets.sln +++ b/Gameboard.ShogiUI.Sockets.sln @@ -9,16 +9,14 @@ 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.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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.xUnitTests", "Gameboard.ShogiUI.xUnitTests\Gameboard.ShogiUI.xUnitTests.csproj", "{12530716-C11E-40CE-9F71-CCCC243F03E1}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain.UnitTests", "Shogi.Domain.UnitTests\Shogi.Domain.UnitTests.csproj", "{F256989E-B6AF-4731-9DB4-88991C40B2CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi.AcceptanceTests", "Shogi.AcceptanceTests\Shogi.AcceptanceTests.csproj", "{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,18 +31,10 @@ 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 - {DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.Build.0 = Release|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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 - {12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12530716-C11E-40CE-9F71-CCCC243F03E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12530716-C11E-40CE-9F71-CCCC243F03E1}.Release|Any CPU.Build.0 = Release|Any CPU {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -53,14 +43,17 @@ Global {F256989E-B6AF-4731-9DB4-88991C40B2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.Build.0 = Release|Any CPU + {F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E} - {12530716-C11E-40CE-9F71-CCCC243F03E1} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E} {F256989E-B6AF-4731-9DB4-88991C40B2CE} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E} + {F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB} diff --git a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs index 15fe9cf..27d9d2f 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs @@ -21,15 +21,18 @@ namespace Gameboard.ShogiUI.Sockets.Controllers private readonly IGameboardManager gameboardManager; private readonly IGameboardRepository gameboardRepository; private readonly ISocketConnectionManager communicationManager; + private readonly IModelMapper mapper; public GameController( IGameboardRepository repository, IGameboardManager manager, - ISocketConnectionManager communicationManager) + ISocketConnectionManager communicationManager, + IModelMapper mapper) { gameboardManager = manager; gameboardRepository = repository; this.communicationManager = communicationManager; + this.mapper = mapper; } [HttpPost("JoinCode")] @@ -75,32 +78,35 @@ namespace Gameboard.ShogiUI.Sockets.Controllers { return NotFound(); } - if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id)) + if (user == null || (session.Player1Name != user.Id && session.Player2Name != user.Id)) { return Forbid("User is not seated at this game."); } - var move = request.Move; - var moveModel = move.PieceFromCaptured.HasValue - ? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion) - : new Models.Move(move.From!, move.To, move.IsPromotion); - var moveSuccess = session.Shogi.Move(moveModel); - - if (moveSuccess) + try { - var createSuccess = await gameboardRepository.CreateBoardState(session); - if (!createSuccess) - { - throw new ApplicationException("Unable to persist board state."); - } - await communicationManager.BroadcastToPlayers(new MoveResponse - { - GameName = session.Name, - PlayerName = user.Id - }, session.Player1.Id, session.Player2?.Id); + var move = request.Move; + if (move.PieceFromCaptured.HasValue) + session.Move(mapper.Map(move.PieceFromCaptured.Value), move.To); + else if (!string.IsNullOrWhiteSpace(move.From)) + session.Move(move.From, move.To, move.IsPromotion); + + await gameboardRepository.CreateBoardState(session); + await communicationManager.BroadcastToPlayers( + new MoveResponse + { + GameName = session.Name, + PlayerName = user.Id + }, + session.Player1Name, + session.Player2Name); + return Ok(); } - return Conflict("Illegal move."); + catch (InvalidOperationException ex) + { + return Conflict(ex.Message); + } } // TODO: Use JWT tokens for guests so they can authenticate and use API routes, too. @@ -128,28 +134,15 @@ namespace Gameboard.ShogiUI.Sockets.Controllers public async Task PostSession([FromBody] PostSession request) { var user = await ReadUserOrThrow(); - var session = new Models.SessionMetadata(request.Name, request.IsPrivate, user!); - var success = await gameboardRepository.CreateSession(session); - - if (success) + var session = new Shogi.Domain.SessionMetadata(request.Name, request.IsPrivate, user.Id); + await gameboardRepository.CreateSession(session); + await communicationManager.BroadcastToAll(new CreateGameResponse { - try - { + Game = mapper.Map(session), + PlayerName = user.Id + }); - await communicationManager.BroadcastToAll(new CreateGameResponse - { - Game = session.ToServiceModel(), - PlayerName = user.Id - }); - } - catch (Exception e) - { - Console.Error.WriteLine("Error broadcasting during PostSession"); - } - - return Ok(); - } - return Conflict(); + return Ok(); } @@ -174,40 +167,16 @@ namespace Gameboard.ShogiUI.Sockets.Controllers { BoardState = new BoardState { - Board = null, - Player1Hand = session.Player1Hand.Select(MapPiece).ToList(), - Player2Hand = session.Player2Hand.Select(MapPiece).ToList(), - PlayerInCheck = session.InCheck.HasValue ? Map(session.InCheck.Value) : null + Board = mapper.Map(session.BoardState), + Player1Hand = session.Player1Hand.Select(mapper.Map).ToList(), + Player2Hand = session.Player2Hand.Select(mapper.Map).ToList(), + PlayerInCheck = mapper.Map(session.InCheck) }, GameName = session.Name, Player1 = session.Player1Name, Player2 = session.Player2Name }; - return this.Ok(response); - - static WhichPlayer Map(Shogi.Domain.WhichPlayer whichPlayer) - { - return whichPlayer == Shogi.Domain.WhichPlayer.Player1 - ? WhichPlayer.Player1 - : WhichPlayer.Player2; - } - static Piece MapPiece(Shogi.Domain.Pieces.Piece piece) - { - var owner = Map(piece.Owner); - var whichPiece = piece.WhichPiece switch - { - Shogi.Domain.WhichPiece.King => WhichPiece.King, - Shogi.Domain.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral, - Shogi.Domain.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral, - Shogi.Domain.WhichPiece.Bishop => WhichPiece.Bishop, - Shogi.Domain.WhichPiece.Rook => WhichPiece.Rook, - Shogi.Domain.WhichPiece.Knight => WhichPiece.Knight, - Shogi.Domain.WhichPiece.Lance => WhichPiece.Lance, - Shogi.Domain.WhichPiece.Pawn => WhichPiece.Pawn, - _ => throw new ArgumentException($"Unknown value for {nameof(WhichPiece)}") - }; - return new Piece { IsPromoted = piece.IsPromoted, Owner = owner, WhichPiece = whichPiece }; - } + return Ok(response); } [HttpGet] @@ -217,18 +186,18 @@ namespace Gameboard.ShogiUI.Sockets.Controllers var sessions = await gameboardRepository.ReadSessionMetadatas(); var sessionsJoinedByUser = sessions - .Where(s => s.IsSeated(user)) - .Select(s => s.ToServiceModel()) + .Where(s => s.IsSeated(user.Id)) + .Select(s => mapper.Map(s)) .ToList(); var sessionsNotJoinedByUser = sessions - .Where(s => !s.IsSeated(user)) - .Select(s => s.ToServiceModel()) + .Where(s => !s.IsSeated(user.Id)) + .Select(s => mapper.Map(s)) .ToList(); return new GetSessionsResponse { - PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser), - AllOtherSessions = new Collection(sessionsNotJoinedByUser) + PlayerHasJoinedSessions = sessionsJoinedByUser, + AllOtherSessions = sessionsNotJoinedByUser }; } @@ -246,13 +215,12 @@ namespace Gameboard.ShogiUI.Sockets.Controllers return this.Conflict("This session already has two seated players and is full."); } - session.SetPlayer2(user); - var success = await gameboardRepository.UpdateSession(session); - if (!success) return this.Problem(detail: "Unable to update session."); + session.SetPlayer2(user.Id); + await gameboardRepository.UpdateSession(session); - var opponentName = user.Id == session.Player1.Id - ? session.Player2!.Id - : session.Player1.Id; + var opponentName = user.Id == session.Player1 + ? session.Player2! + : session.Player1; await communicationManager.BroadcastToPlayers(new JoinGameResponse { GameName = session.Name, diff --git a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs index 4824284..dc8f9ec 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs @@ -51,13 +51,13 @@ namespace Gameboard.ShogiUI.Sockets.Controllers public async Task GuestLogout() { var signoutTask = HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - + var userId = User?.UserId(); if (!string.IsNullOrEmpty(userId)) { connectionManager.Unsubscribe(userId); } - + await signoutTask; return Ok(); } @@ -68,10 +68,8 @@ namespace Gameboard.ShogiUI.Sockets.Controllers var user = await gameboardManager.ReadUser(User); if (user == null) { - if (await gameboardManager.CreateUser(User)) - { - user = await gameboardManager.ReadUser(User); - } + await gameboardManager.CreateUser(User); + user = await gameboardManager.ReadUser(User); } if (user == null) @@ -92,11 +90,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers { // Create a guest user. var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString()); - var success = await gameboardRepository.CreateUser(newUser); - if (!success) - { - return Conflict(); - } + await gameboardRepository.CreateUser(newUser); var identity = newUser.CreateClaimsIdentity(); await HttpContext.SignInAsync( diff --git a/Gameboard.ShogiUI.Sockets/Managers/GameboardManager.cs b/Gameboard.ShogiUI.Sockets/Managers/GameboardManager.cs index 5cc9f04..9ca34c1 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/GameboardManager.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/GameboardManager.cs @@ -7,68 +7,68 @@ using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Managers { - public interface IGameboardManager - { - Task AssignPlayer2ToSession(string sessionName, User user); - Task ReadUser(ClaimsPrincipal user); - Task CreateUser(ClaimsPrincipal user); - } + public interface IGameboardManager + { + Task AssignPlayer2ToSession(string sessionName, User user); + Task ReadUser(ClaimsPrincipal user); + Task CreateUser(ClaimsPrincipal user); + } - public class GameboardManager : IGameboardManager - { - private readonly IGameboardRepository repository; + public class GameboardManager : IGameboardManager + { + private readonly IGameboardRepository repository; - public GameboardManager(IGameboardRepository repository) - { - this.repository = repository; - } + public GameboardManager(IGameboardRepository repository) + { + this.repository = repository; + } - public Task CreateUser(ClaimsPrincipal principal) - { - var id = principal.UserId(); - if (string.IsNullOrEmpty(id)) - { - return Task.FromResult(false); - } + public async Task CreateUser(ClaimsPrincipal principal) + { + var id = principal.UserId(); + if (string.IsNullOrEmpty(id)) + { + throw new InvalidOperationException("Cannot create user from given claims."); + } - var user = principal.IsGuest() - ? User.CreateGuestUser(id) - : User.CreateMsalUser(id); + var user = principal.IsGuest() + ? User.CreateGuestUser(id) + : User.CreateMsalUser(id); - return repository.CreateUser(user); - } + await repository.CreateUser(user); + return user; + } - public Task ReadUser(ClaimsPrincipal principal) - { - var userId = principal.UserId(); - if (!string.IsNullOrEmpty(userId)) - { - return repository.ReadUser(userId); - } + public Task ReadUser(ClaimsPrincipal principal) + { + var userId = principal.UserId(); + if (!string.IsNullOrEmpty(userId)) + { + return repository.ReadUser(userId); + } - return Task.FromResult(null); - } + return Task.FromResult(null); + } - public async Task CreateJoinCode(string sessionName, string playerName) - { - //var session = await repository.GetGame(sessionName); - //if (playerName == session?.Player1) - //{ - // return await repository.PostJoinCode(sessionName, playerName); - //} - return string.Empty; - } + public async Task CreateJoinCode(string sessionName, string playerName) + { + //var session = await repository.GetGame(sessionName); + //if (playerName == session?.Player1) + //{ + // return await repository.PostJoinCode(sessionName, playerName); + //} + return string.Empty; + } - public async Task AssignPlayer2ToSession(string sessionName, User user) - { - var session = await repository.ReadSessionMetaData(sessionName); - if (session != null && !session.IsPrivate && session.Player2 == null) - { - session.SetPlayer2(user); - return await repository.UpdateSession(session); - } - return false; - } - } + public async Task AssignPlayer2ToSession(string sessionName, User user) + { + var session = await repository.ReadSessionMetaData(sessionName); + if (session != null && !session.IsPrivate && session.Player2 == null) + { + session.SetPlayer2(user.Id); + await repository.UpdateSession(session); + } + } + } } diff --git a/Gameboard.ShogiUI.Sockets/Managers/ModelMapper.cs b/Gameboard.ShogiUI.Sockets/Managers/ModelMapper.cs new file mode 100644 index 0000000..ef01ef2 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets/Managers/ModelMapper.cs @@ -0,0 +1,100 @@ +using Gameboard.ShogiUI.Sockets.ServiceModels.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainWhichPiece = Shogi.Domain.WhichPiece; +using DomainWhichPlayer = Shogi.Domain.WhichPlayer; + +namespace Gameboard.ShogiUI.Sockets.Managers +{ + public class ModelMapper : IModelMapper + { + public WhichPlayer Map(DomainWhichPlayer whichPlayer) + { + return whichPlayer switch + { + DomainWhichPlayer.Player1 => WhichPlayer.Player1, + DomainWhichPlayer.Player2 => WhichPlayer.Player2, + _ => throw new ArgumentException("Unrecognized value for WhichPlayer", nameof(whichPlayer)) + }; + } + + public WhichPlayer? Map(DomainWhichPlayer? whichPlayer) + { + return whichPlayer.HasValue + ? Map(whichPlayer.Value) + : null; + } + + public WhichPiece Map(DomainWhichPiece whichPiece) + { + return whichPiece switch + { + DomainWhichPiece.King => WhichPiece.King, + DomainWhichPiece.GoldGeneral => WhichPiece.GoldGeneral, + DomainWhichPiece.SilverGeneral => WhichPiece.SilverGeneral, + DomainWhichPiece.Bishop => WhichPiece.Bishop, + DomainWhichPiece.Rook => WhichPiece.Rook, + DomainWhichPiece.Knight => WhichPiece.Knight, + DomainWhichPiece.Lance => WhichPiece.Lance, + DomainWhichPiece.Pawn => WhichPiece.Pawn, + _ => throw new ArgumentException("Unrecognized value", nameof(whichPiece)), + }; + } + + public DomainWhichPiece Map(WhichPiece whichPiece) + { + return whichPiece switch + { + WhichPiece.King => DomainWhichPiece.King, + WhichPiece.GoldGeneral => DomainWhichPiece.GoldGeneral, + WhichPiece.SilverGeneral => DomainWhichPiece.SilverGeneral, + WhichPiece.Bishop => DomainWhichPiece.Bishop, + WhichPiece.Rook => DomainWhichPiece.Rook, + WhichPiece.Knight => DomainWhichPiece.Knight, + WhichPiece.Lance => DomainWhichPiece.Lance, + WhichPiece.Pawn => DomainWhichPiece.Pawn, + _ => throw new ArgumentException("Unrecognized value", nameof(whichPiece)), + }; + } + + public SessionMetadata Map(Shogi.Domain.SessionMetadata session) + { + return new SessionMetadata + { + Name = session.Name, + Player1 = session.Player1, + Player2 = session.Player2, + IsPrivate = session.IsPrivate + }; + } + + public Piece Map(Shogi.Domain.Pieces.Piece piece) + { + return new Piece { IsPromoted = piece.IsPromoted, Owner = Map(piece.Owner), WhichPiece = Map(piece.WhichPiece) }; + } + + public Dictionary Map(IDictionary boardState) + { + return boardState.ToDictionary(kvp => kvp.Key, kvp => MapNullable(kvp.Value)); + } + + public Piece? MapNullable(Shogi.Domain.Pieces.Piece? piece) + { + if (piece == null) return null; + return Map(piece); + } + } + + public interface IModelMapper + { + WhichPlayer Map(DomainWhichPlayer whichPlayer); + WhichPlayer? Map(DomainWhichPlayer? whichPlayer); + WhichPiece Map(DomainWhichPiece whichPiece); + DomainWhichPiece Map(WhichPiece value); + SessionMetadata Map(Shogi.Domain.SessionMetadata session); + Piece Map(Shogi.Domain.Pieces.Piece p); + Piece? MapNullable(Shogi.Domain.Pieces.Piece? p); + Dictionary Map(IDictionary boardState); + } +} diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs index ecace67..6c35ccf 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs @@ -12,10 +12,10 @@ namespace Gameboard.ShogiUI.Sockets.Managers { public interface ISocketConnectionManager { - Task BroadcastToAll(IResponse response); + Task BroadcastToAll(ISocketResponse response); void Subscribe(WebSocket socket, string playerName); void Unsubscribe(string playerName); - Task BroadcastToPlayers(IResponse response, params string?[] playerNames); + Task BroadcastToPlayers(ISocketResponse response, params string?[] playerNames); } /// @@ -45,7 +45,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers connections.TryRemove(playerName, out _); } - public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames) + public async Task BroadcastToPlayers(ISocketResponse response, params string?[] playerNames) { var tasks = new List(playerNames.Length); foreach (var name in playerNames) @@ -59,7 +59,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers } await Task.WhenAll(tasks); } - public Task BroadcastToAll(IResponse response) + public Task BroadcastToAll(ISocketResponse response) { var message = JsonConvert.SerializeObject(response); logger.LogInformation($"Broadcasting\n{0}", message); diff --git a/Gameboard.ShogiUI.Sockets/Models/Move.cs b/Gameboard.ShogiUI.Sockets/Models/Move.cs deleted file mode 100644 index bbb32f7..0000000 --- a/Gameboard.ShogiUI.Sockets/Models/Move.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Gameboard.ShogiUI.Sockets.ServiceModels.Types; -using Gameboard.ShogiUI.Sockets.Utilities; -using System.Diagnostics; -using System.Numerics; - -namespace Gameboard.ShogiUI.Sockets.Models -{ - [DebuggerDisplay("{From} - {To}")] - public class Move - { - public Vector2? From { get; } // TODO: Use string notation - public bool IsPromotion { get; } - public WhichPiece? PieceFromHand { get; } - public Vector2 To { get; } - - public Move(Vector2 from, Vector2 to, bool isPromotion = false) - { - From = from; - To = to; - IsPromotion = isPromotion; - } - public Move(WhichPiece pieceFromHand, Vector2 to) - { - PieceFromHand = pieceFromHand; - To = to; - } - - /// - /// Constructor to represent moving a piece on the Board to another position on the Board. - /// - /// Position the piece is being moved from. - /// Position the piece is being moved to. - /// If the moving piece should be promoted. - public Move(string fromNotation, string toNotation, bool isPromotion = false) - { - From = NotationHelper.FromBoardNotation(fromNotation); - To = NotationHelper.FromBoardNotation(toNotation); - IsPromotion = isPromotion; - } - - /// - /// Constructor to represent moving a piece from the Hand to the Board. - /// - /// The piece being moved from the Hand to the Board. - /// Position the piece is being moved to. - /// If the moving piece should be promoted. - public Move(WhichPiece pieceFromHand, string toNotation, bool isPromotion = false) - { - From = null; - PieceFromHand = pieceFromHand; - To = NotationHelper.FromBoardNotation(toNotation); - IsPromotion = isPromotion; - } - - public ServiceModels.Types.Move ToServiceModel() => new() - { - From = From.HasValue ? NotationHelper.ToBoardNotation(From.Value) : null, - IsPromotion = IsPromotion, - PieceFromCaptured = PieceFromHand.HasValue ? PieceFromHand : null, - To = NotationHelper.ToBoardNotation(To) - }; - } -} diff --git a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs b/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs deleted file mode 100644 index 9747608..0000000 --- a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Gameboard.ShogiUI.Sockets.Models -{ - /// - /// A representation of a Session without the board and game-rules. - /// - public class SessionMetadata - { - public string Name { get; } - public User Player1 { get; } - public User? Player2 { get; private set; } - public bool IsPrivate { get; } - - public SessionMetadata(string name, bool isPrivate, User player1, User? player2 = null) - { - Name = name; - IsPrivate = isPrivate; - Player1 = player1; - Player2 = player2; - } - public SessionMetadata(Session sessionModel) - { - Name = sessionModel.Name; - IsPrivate = sessionModel.IsPrivate; - Player1 = sessionModel.Player1; - Player2 = sessionModel.Player2; - } - - public void SetPlayer2(User user) - { - Player2 = user; - } - - public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id; - - public ServiceModels.Types.Session ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName); - } -} diff --git a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json index 05ff5bf..fcafa32 100644 --- a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json +++ b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json @@ -1,4 +1,15 @@ { + "profiles": { + "Kestrel": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + }, "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, @@ -7,23 +18,5 @@ "applicationUrl": "http://localhost:50728/", "sslPort": 44315 } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "/swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:5100" - } } } \ No newline at end of file diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs index 75249e1..8b5ac54 100644 --- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs +++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs @@ -1,4 +1,6 @@ -namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels +using Shogi.Domain; + +namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels { public class SessionDocument : CouchDocument { @@ -17,12 +19,12 @@ Player2Id = string.Empty; } - public SessionDocument(Models.SessionMetadata sessionMetaData) + public SessionDocument(SessionMetadata sessionMetaData) : base(sessionMetaData.Name, WhichDocumentType.Session) { Name = sessionMetaData.Name; - Player1Id = sessionMetaData.Player1.Id; - Player2Id = sessionMetaData.Player2?.Id; + Player1Id = sessionMetaData.Player1; + Player2Id = sessionMetaData.Player2; IsPrivate = sessionMetaData.IsPrivate; } } diff --git a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs index 075e388..0022591 100644 --- a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs +++ b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs @@ -17,13 +17,13 @@ namespace Gameboard.ShogiUI.Sockets.Repositories { public interface IGameboardRepository { - Task CreateBoardState(Session session); - Task CreateSession(Models.SessionMetadata session); - Task CreateUser(Models.User user); - Task> ReadSessionMetadatas(); + Task CreateBoardState(Session session); + Task CreateSession(SessionMetadata session); + Task CreateUser(Models.User user); + Task> ReadSessionMetadatas(); Task ReadSession(string name); - Task UpdateSession(Models.SessionMetadata session); - Task ReadSessionMetaData(string name); + Task UpdateSession(SessionMetadata session); + Task ReadSessionMetaData(string name); Task ReadUser(string userName); } @@ -48,7 +48,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories this.logger = logger; } - public async Task> ReadSessionMetadatas() + public async Task> ReadSessionMetadatas() { var queryParams = new QueryBuilder { { "include_docs", "true" } }.ToQueryString(); var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}"); @@ -57,7 +57,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories if (result != null) { var groupedBySession = result.rows.GroupBy(row => row.id); - var sessions = new List(result.total_rows / 3); + var sessions = new List(result.total_rows / 3); foreach (var group in groupedBySession) { /** @@ -72,12 +72,12 @@ namespace Gameboard.ShogiUI.Sockets.Repositories if (session != null && player1Doc != null) { var player2 = player2Doc == null ? null : new Models.User(player2Doc); - sessions.Add(new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2)); + sessions.Add(new SessionMetadata(session.Name, session.IsPrivate, player1Doc.Id, player2?.Id)); } } - return new Collection(sessions); + return new Collection(sessions); } - return new Collection(Array.Empty()); + return new Collection(Array.Empty()); } public async Task ReadSession(string name) @@ -130,7 +130,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories return null; } - public async Task ReadSessionMetaData(string name) + public async Task ReadSessionMetaData(string name) { var queryParams = new QueryBuilder { @@ -159,7 +159,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories if (session != null && player1Doc != null) { var player2 = player2Doc == null ? null : new Models.User(player2Doc); - return new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2); + return new SessionMetadata(session.Name, session.IsPrivate, player1Doc.Id, player2?.Id); } } return null; @@ -168,22 +168,15 @@ namespace Gameboard.ShogiUI.Sockets.Repositories /// /// Saves a snapshot of board state and the most recent move. /// - public async Task CreateBoardState(Session session) + public async Task CreateBoardState(Session session) { - Piece? MapPiece(Shogi.Domain.Pieces.Piece? piece) - { - return piece == null - ? null - : new Piece { IsPromoted = piece.IsPromoted, Owner = piece.Owner, WhichPiece = piece.WhichPiece }; - } - var boardStateDocument = new BoardStateDocument(session.Name, session); var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson); var response = await client.PostAsync(string.Empty, content); - return response.IsSuccessStatusCode; + response.EnsureSuccessStatusCode(); } - public async Task CreateSession(Models.SessionMetadata session) + public async Task CreateSession(SessionMetadata session) { var sessionDocument = new SessionDocument(session); var sessionContent = new StringContent(JsonConvert.SerializeObject(sessionDocument), Encoding.UTF8, ApplicationJson); @@ -195,16 +188,15 @@ namespace Gameboard.ShogiUI.Sockets.Repositories if ((await postSessionDocumentTask).IsSuccessStatusCode) { var response = await client.PostAsync(string.Empty, boardStateContent); - return response.IsSuccessStatusCode; + response.EnsureSuccessStatusCode(); } - return false; } - public async Task UpdateSession(Models.SessionMetadata session) + public async Task UpdateSession(SessionMetadata session) { // GET existing session to get revisionId. var readResponse = await client.GetAsync(session.Name); - if (!readResponse.IsSuccessStatusCode) return false; + readResponse.EnsureSuccessStatusCode(); var sessionDocument = JsonConvert.DeserializeObject(await readResponse.Content.ReadAsStringAsync()); // PUT the document with the revisionId. @@ -214,7 +206,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories }; var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson); var response = await client.PutAsync(couchModel.Id, content); - return response.IsSuccessStatusCode; + response.EnsureSuccessStatusCode(); } //public async Task PutJoinPublicSession(PutJoinPublicSession request) //{ @@ -285,12 +277,12 @@ namespace Gameboard.ShogiUI.Sockets.Repositories return null; } - public async Task CreateUser(Models.User user) + public async Task CreateUser(Models.User user) { var couchModel = new UserDocument(user.Id, user.DisplayName, user.LoginPlatform); var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson); var response = await client.PostAsync(string.Empty, content); - return response.IsSuccessStatusCode; + response.EnsureSuccessStatusCode(); } public void ReadMoveHistory() diff --git a/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs b/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs index fc00288..766efa6 100644 --- a/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs +++ b/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs @@ -28,8 +28,8 @@ namespace Gameboard.ShogiUI.Sockets if (user == null) { var newUser = Models.User.CreateMsalUser(nameClaim.Value); - var success = await gameboardRepository.CreateUser(newUser); - if (success) user = newUser; + await gameboardRepository.CreateUser(newUser); + user = newUser; } if (user != null) diff --git a/Gameboard.ShogiUI.Sockets/Startup.cs b/Gameboard.ShogiUI.Sockets/Startup.cs index e0c7407..882bada 100644 --- a/Gameboard.ShogiUI.Sockets/Startup.cs +++ b/Gameboard.ShogiUI.Sockets/Startup.cs @@ -58,6 +58,7 @@ namespace Gameboard.ShogiUI.Sockets var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/"; c.BaseAddress = new Uri(baseUrl); }); + services.AddTransient(); services .AddControllers() @@ -128,13 +129,17 @@ namespace Gameboard.ShogiUI.Sockets }; }); - // Remove default HttpClient logging. - services.RemoveAll(); + services.AddHttpLogging(options => + { + options.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.Request; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISocketService socketConnectionManager) { + app.UseHttpLogging(); + var origins = new[] { "http://localhost:3000", "https://localhost:3000", "http://127.0.0.1:3000", "https://127.0.0.1:3000", @@ -147,25 +152,25 @@ namespace Gameboard.ShogiUI.Sockets if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - var client = PublicClientApplicationBuilder - .Create(Configuration["AzureAd:ClientId"]) - .WithLogging( - (level, message, pii) => - { - Console.WriteLine(message); - }, - LogLevel.Verbose, - true, - true - ) - .Build(); + //var client = PublicClientApplicationBuilder + // .Create(Configuration["AzureAd:ClientId"]) + // .WithLogging( + // (level, message, pii) => + // { + // Console.WriteLine(message); + // }, + // LogLevel.Verbose, + // true, + // true + // ) + // .Build(); } else { app.UseHsts(); } app - .UseRequestResponseLogging() + //.UseRequestResponseLogging() .UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials()) .UseRouting() .UseAuthentication() diff --git a/Gameboard.ShogiUI.UnitTests/Gameboard.ShogiUI.UnitTests.csproj b/Gameboard.ShogiUI.UnitTests/Gameboard.ShogiUI.UnitTests.csproj deleted file mode 100644 index 16999c2..0000000 --- a/Gameboard.ShogiUI.UnitTests/Gameboard.ShogiUI.UnitTests.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net6.0 - - - - - - - - - - - - - - - - - diff --git a/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs b/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs deleted file mode 100644 index 983ec6f..0000000 --- a/Gameboard.ShogiUI.UnitTests/PathFinding/PathFinder2DShould.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PathFinding; -using System.Numerics; - -namespace Gameboard.ShogiUI.UnitTests.PathFinding -{ - [TestClass] - public class PathFinder2DShould - { - [TestMethod] - public void Maths() - { - var result = PathFinder2D.IsPathable( - new Vector2(2, 2), - new Vector2(7, 7), - new Vector2(1, 1) - ); - result.Should().BeTrue(); - - result = PathFinder2D.IsPathable( - new Vector2(2, 2), - new Vector2(7, 7), - new Vector2(0, 0) - ); - result.Should().BeFalse(); - - result = PathFinder2D.IsPathable( - new Vector2(2, 2), - new Vector2(7, 7), - new Vector2(-1, 1) - ); - result.Should().BeFalse(); - } - } -} diff --git a/Gameboard.ShogiUI.UnitTests/PathFinding/PlanarCollectionShould.cs b/Gameboard.ShogiUI.UnitTests/PathFinding/PlanarCollectionShould.cs deleted file mode 100644 index 2ac90d6..0000000 --- a/Gameboard.ShogiUI.UnitTests/PathFinding/PlanarCollectionShould.cs +++ /dev/null @@ -1,57 +0,0 @@ -using AutoFixture; -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; - -namespace Gameboard.ShogiUI.UnitTests.PathFinding -{ - [TestClass] - public class PlanarCollectionShould - { - [TestMethod] - public void Index() - { - // Arrange - var collection = new TestPlanarCollection(); - var expected1 = new SimpleElement(1); - var expected2 = new SimpleElement(2); - - // Act - collection[0, 0] = expected1; - collection[2, 1] = expected2; - - // Assert - collection[0, 0].Should().Be(expected1); - collection[2, 1].Should().Be(expected2); - } - - [TestMethod] - public void Iterate() - { - // Arrange - var planarCollection = new TestPlanarCollection(); - planarCollection[0, 0] = new SimpleElement(1); - planarCollection[0, 1] = new SimpleElement(2); - planarCollection[0, 2] = new SimpleElement(3); - planarCollection[1, 0] = new SimpleElement(4); - planarCollection[1, 1] = new SimpleElement(5); - - // Act - var actual = new List(); - foreach (var elem in planarCollection) - actual.Add(elem); - - // Assert - using (new AssertionScope()) - { - actual[0].Number.Should().Be(1); - actual[1].Number.Should().Be(2); - actual[2].Number.Should().Be(3); - actual[3].Number.Should().Be(4); - actual[4].Number.Should().Be(5); - } - } - } -} diff --git a/Gameboard.ShogiUI.UnitTests/PathFinding/TestPlanarCollection.cs b/Gameboard.ShogiUI.UnitTests/PathFinding/TestPlanarCollection.cs deleted file mode 100644 index 4cb3a74..0000000 --- a/Gameboard.ShogiUI.UnitTests/PathFinding/TestPlanarCollection.cs +++ /dev/null @@ -1,48 +0,0 @@ -using PathFinding; -using System.Collections; -using System.Collections.Generic; -using System.Numerics; - -namespace Gameboard.ShogiUI.UnitTests.PathFinding -{ - public class SimpleElement : IPlanarElement - { - public int Number { get; } - public MoveSet MoveSet => null; - public bool IsUpsideDown => false; - - public SimpleElement(int number) - { - Number = number; - } - } - - public class TestPlanarCollection : IPlanarCollection - { - private readonly SimpleElement[,] array; - public TestPlanarCollection() - { - array = new SimpleElement[3, 3]; - } - public SimpleElement this[int x, int y] - { - get => array[x, y]; - set => array[x, y] = value; - } - public SimpleElement this[Vector2 vector] - { - get => this[(int)vector.X, (int)vector.Y]; - set => this[(int)vector.X, (int)vector.Y] = value; - } - - public IEnumerator GetEnumerator() - { - foreach (var e in array) - yield return e; - } - //IEnumerator IEnumerable.GetEnumerator() - //{ - // return array.GetEnumerator(); - //} - } -} diff --git a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs deleted file mode 100644 index e13dea3..0000000 --- a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentAssertions; -using Gameboard.ShogiUI.Sockets.Models; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Linq; -using System.Numerics; -using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer; -using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece; -namespace Gameboard.ShogiUI.UnitTests.Rules -{ - [TestClass] - public class ShogiBoardShould - { - - } -} diff --git a/Gameboard.ShogiUI.xUnitTests/GameShould.cs b/Gameboard.ShogiUI.xUnitTests/GameShould.cs deleted file mode 100644 index 451d4f3..0000000 --- a/Gameboard.ShogiUI.xUnitTests/GameShould.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FluentAssertions; -using Gameboard.ShogiUI.Sockets.ServiceModels.Types; -using Xunit; - -namespace Gameboard.ShogiUI.xUnitTests -{ - public class GameShould - { - [Fact] - public void DiscardNullPLayers() - { - var game = new Session("Test", "P1", null); - - game.Players.Count.Should().Be(1); - } - } -} diff --git a/Gameboard.ShogiUI.xUnitTests/NotationHelperShould.cs b/Gameboard.ShogiUI.xUnitTests/NotationHelperShould.cs deleted file mode 100644 index 45c4020..0000000 --- a/Gameboard.ShogiUI.xUnitTests/NotationHelperShould.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FluentAssertions; -using Gameboard.ShogiUI.Sockets.Utilities; -using System.Numerics; -using Xunit; - -namespace Gameboard.ShogiUI.xUnitTests -{ - public class NotationHelperShould - { - [Fact] - public void TranslateVectorsToNotation() - { - NotationHelper.ToBoardNotation(2, 2).Should().Be("C3"); - } - - [Fact] - public void TranslateNotationToVectors() - { - NotationHelper.FromBoardNotation("C3").Should().Be(new Vector2(2, 2)); - } - } -} diff --git a/Gameboard.ShogiUI.xUnitTests/xunit.runner.json b/Gameboard.ShogiUI.xUnitTests/xunit.runner.json deleted file mode 100644 index 1d28022..0000000 --- a/Gameboard.ShogiUI.xUnitTests/xunit.runner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "methodDisplay": "method" -} diff --git a/PathFinding/Direction.cs b/PathFinding/Direction.cs deleted file mode 100644 index 2ee825d..0000000 --- a/PathFinding/Direction.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Numerics; - -namespace PathFinding -{ - public static class Direction - { - public static readonly Vector2 Up = new(0, 1); - public static readonly Vector2 Down = new(0, -1); - public static readonly Vector2 Left = new(-1, 0); - public static readonly Vector2 Right = new(1, 0); - public static readonly Vector2 UpLeft = new(-1, 1); - public static readonly Vector2 UpRight = new(1, 1); - public static readonly Vector2 DownLeft = new(-1, -1); - public static readonly Vector2 DownRight = new(1, -1); - public static readonly Vector2 KnightLeft = new(-1, 2); - public static readonly Vector2 KnightRight = new(1, 2); - } -} diff --git a/PathFinding/Distance.cs b/PathFinding/Distance.cs deleted file mode 100644 index 339296d..0000000 --- a/PathFinding/Distance.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PathFinding -{ - public enum Distance - { - OneStep, - MultiStep - } -} \ No newline at end of file diff --git a/PathFinding/IPlanarCollection.cs b/PathFinding/IPlanarCollection.cs deleted file mode 100644 index 0075dcd..0000000 --- a/PathFinding/IPlanarCollection.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using System.Numerics; - -namespace PathFinding -{ - public interface IPlanarCollection where T : IPlanarElement - { - T? this[Vector2 vector] { get; set; } - T? this[int x, int y] { get; set; } - } -} diff --git a/PathFinding/IPlanarElement.cs b/PathFinding/IPlanarElement.cs deleted file mode 100644 index 5d3fde4..0000000 --- a/PathFinding/IPlanarElement.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace PathFinding -{ - public interface IPlanarElement - { - MoveSet MoveSet { get; } - bool IsUpsideDown { get; } - } -} diff --git a/PathFinding/Move.cs b/PathFinding/Move.cs deleted file mode 100644 index 5ff2c8e..0000000 --- a/PathFinding/Move.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Diagnostics; -using System.Numerics; - -namespace PathFinding -{ - [DebuggerDisplay("{Direction} - {Distance}")] - public class Move - { - public Vector2 Direction { get; } - public Distance Distance { get; } - public Move(Vector2 direction, Distance distance = Distance.OneStep) - { - Direction = direction; - Distance = distance; - } - } -} diff --git a/PathFinding/MoveSet.cs b/PathFinding/MoveSet.cs deleted file mode 100644 index f6c7260..0000000 --- a/PathFinding/MoveSet.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Numerics; - -namespace PathFinding -{ - public class MoveSet - { - private readonly IPlanarElement element; - private readonly ICollection moves; - private readonly ICollection upsidedownMoves; - - public MoveSet(IPlanarElement element, ICollection moves) - { - this.element = element; - this.moves = moves; - upsidedownMoves = moves.Select(_ => new Move(Vector2.Negate(_.Direction), _.Distance)).ToList(); - } - - public ICollection GetMoves() => element.IsUpsideDown ? upsidedownMoves : moves; - - } -} diff --git a/PathFinding/PathFinder2D.cs b/PathFinding/PathFinder2D.cs deleted file mode 100644 index 6a04823..0000000 --- a/PathFinding/PathFinder2D.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; - -namespace PathFinding -{ - public class PathFinder2D where T : IPlanarElement - { - /// Guaranteed to be non-null. - /// - public delegate void Callback(T collider, Vector2 position); - - private readonly IPlanarCollection collection; - private readonly int width; - private readonly int height; - - /// Horizontal size, in steps, of the pathable plane. - /// Vertical size, in steps, of the pathable plane. - public PathFinder2D(IPlanarCollection collection, int width, int height) - { - this.collection = collection; - this.width = width; - this.height = height; - } - - /// - /// Navigate the collection such that each "step" is always towards the destination, respecting the Paths available to the element at origin. - /// - /// The pathing element. - /// The starting location. - /// The destination. - /// Do cool stuff here. - /// True if the element reached the destination. - public bool PathTo(Vector2 origin, Vector2 destination, Callback? callback = null) - { - if (destination.X > width - 1 || destination.Y > height - 1 || destination.X < 0 || destination.Y < 0) - { - return false; - } - var element = collection[origin]; - if (element == null) return false; - - var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination); - if (!IsPathable(origin, destination, path.Direction)) - { - // Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen. - return false; - } - - var shouldPath = true; - var next = origin; - while (shouldPath && next != destination) - { - next = Vector2.Add(next, path.Direction); - var collider = collection[next]; - if (collider != null) - { - callback?.Invoke(collider, next); - shouldPath = false; - } - else if (path.Distance == Distance.OneStep) - { - shouldPath = false; - } - } - return next == destination; - } - - public void PathEvery(Vector2 from, Callback callback) - { - var element = collection[from]; - if (element == null) - { - return; - } - foreach (var path in element.MoveSet.GetMoves()) - { - var shouldPath = true; - var next = Vector2.Add(from, path.Direction); ; - while (shouldPath && next.X < width && next.Y < height && next.X >= 0 && next.Y >= 0) - { - var collider = collection[(int)next.Y, (int)next.X]; - if (collider != null) - { - callback(collider, next); - shouldPath = false; - } - if (path.Distance == Distance.OneStep) - { - shouldPath = false; - } - next = Vector2.Add(next, path.Direction); - } - } - } - - /// - /// Path the line from origin to destination, ignoring any Paths defined by the element at origin. - /// - public void LinePathTo(Vector2 origin, Vector2 direction, Callback callback) - { - direction = Vector2.Normalize(direction); - - var next = Vector2.Add(origin, direction); - while (next.X >= 0 && next.X < width && next.Y >= 0 && next.Y < height) - { - var element = collection[next]; - if (element != null) callback(element, next); - next = Vector2.Add(next, direction); - } - } - - public static Move FindDirectionTowardsDestination(ICollection paths, Vector2 origin, Vector2 destination) => - paths.Aggregate((a, b) => - { - var distanceA = Vector2.Distance(destination, Vector2.Add(origin, a.Direction)); - var distanceB = Vector2.Distance(destination, Vector2.Add(origin, b.Direction)); - return distanceA < distanceB ? a : b; - }); - - public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction) - { - var next = Vector2.Add(origin, direction); - if (Vector2.Distance(next, destination) >= Vector2.Distance(origin, destination)) return false; - - var slope = (destination.Y - origin.Y) / (destination.X - origin.X); - if (float.IsInfinity(slope)) - { - return next.X == destination.X; - } - else - { - // b = -mx + y - var yIntercept = -slope * origin.X + origin.Y; - // y = mx + b - return next.Y == slope * next.X + yIntercept; - } - } - } -} diff --git a/PathFinding/PathFinding.csproj b/PathFinding/PathFinding.csproj deleted file mode 100644 index 6493b62..0000000 --- a/PathFinding/PathFinding.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - true - 5 - enable - - - - - - - diff --git a/Shogi.AcceptanceTests/AcceptanceTests.cs b/Shogi.AcceptanceTests/AcceptanceTests.cs new file mode 100644 index 0000000..b407d7a --- /dev/null +++ b/Shogi.AcceptanceTests/AcceptanceTests.cs @@ -0,0 +1,16 @@ +namespace Shogi.AcceptanceTests +{ + public class AcceptanceTests + { + public AcceptanceTests() + { + + } + + [Fact] + public void CreateAndReadSession() + { + + } + } +} \ No newline at end of file diff --git a/Gameboard.ShogiUI.xUnitTests/Gameboard.ShogiUI.xUnitTests.csproj b/Shogi.AcceptanceTests/Shogi.AcceptanceTests.csproj similarity index 59% rename from Gameboard.ShogiUI.xUnitTests/Gameboard.ShogiUI.xUnitTests.csproj rename to Shogi.AcceptanceTests/Shogi.AcceptanceTests.csproj index 1de7d82..c5d1063 100644 --- a/Gameboard.ShogiUI.xUnitTests/Gameboard.ShogiUI.xUnitTests.csproj +++ b/Shogi.AcceptanceTests/Shogi.AcceptanceTests.csproj @@ -2,33 +2,23 @@ net6.0 + enable + enable false - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - Always - - - diff --git a/Shogi.AcceptanceTests/Usings.cs b/Shogi.AcceptanceTests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/Shogi.AcceptanceTests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Shogi.Domain/SessionMetadata.cs b/Shogi.Domain/SessionMetadata.cs index 972823c..bcace67 100644 --- a/Shogi.Domain/SessionMetadata.cs +++ b/Shogi.Domain/SessionMetadata.cs @@ -22,5 +22,7 @@ { Player2 = user; } + + public bool IsSeated(string playerName) => playerName == Player1 || playerName == Player2; } }