From 433ab2772aa6b73fa0dc805e994018f337b19848 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Sun, 21 Nov 2021 10:07:35 -0600 Subject: [PATCH] checkpoint --- .../Api/GetSession.cs | 5 +- ...{GetSessionsResponse.cs => GetSessions.cs} | 0 .../Socket/Move.cs | 3 +- .../Types/BoardState.cs | 4 +- .../Types/Game.cs | 59 +- .../Types/Piece.cs | 2 +- .../Types/User.cs | 9 + .../Types/WhichPerspective.cs | 9 + .../Types/WhichPlayer.cs | 8 - .../Controllers/GameController.cs | 415 ++++---- .../Controllers/SocketController.cs | 166 ++-- .../Extensions/LogMiddleware.cs | 71 +- .../Extensions/ModelExtensions.cs | 3 +- .../Managers/SocketConnectionManager.cs | 273 +++--- Gameboard.ShogiUI.Sockets/Models/Piece.cs | 12 +- Gameboard.ShogiUI.Sockets/Models/Session.cs | 50 +- .../Models/SessionMetadata.cs | 74 +- Gameboard.ShogiUI.Sockets/Models/Shogi.cs | 829 ++++++++-------- Gameboard.ShogiUI.Sockets/Models/User.cs | 124 +-- .../Repositories/CouchModels/Piece.cs | 2 +- .../ShogiUserClaimsTransformer.cs | 3 - .../Rules/ShogiBoardShould.cs | 2 +- Gameboard.ShogiUI.xUnitTests/ShogiShould.cs | 920 +++++++++--------- 23 files changed, 1556 insertions(+), 1487 deletions(-) rename Gameboard.ShogiUI.Sockets.ServiceModels/Api/{GetSessionsResponse.cs => GetSessions.cs} (100%) create mode 100644 Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs create mode 100644 Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs delete mode 100644 Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPlayer.cs diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs index d1e3cf7..8fdbfe4 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs @@ -6,7 +6,10 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api public class GetSessionResponse { public Game Game { get; set; } - public WhichPlayer PlayerPerspective { get; set; } + /// + /// The perspective on the game of the requesting user. + /// + public WhichPerspective PlayerPerspective { get; set; } public BoardState BoardState { get; set; } public IList MoveHistory { get; set; } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessionsResponse.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs similarity index 100% rename from Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessionsResponse.cs rename to Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs index 3faf4b5..0b19a44 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Move.cs @@ -1,5 +1,4 @@ -using Gameboard.ShogiUI.Sockets.ServiceModels.Api; -using Gameboard.ShogiUI.Sockets.ServiceModels.Types; +using Gameboard.ShogiUI.Sockets.ServiceModels.Types; namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket { diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs index 398ba4a..b6d0a98 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs @@ -8,7 +8,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types public Dictionary Board { get; set; } = new Dictionary(); public IReadOnlyCollection Player1Hand { get; set; } = Array.Empty(); public IReadOnlyCollection Player2Hand { get; set; } = Array.Empty(); - public WhichPlayer? PlayerInCheck { get; set; } - public WhichPlayer WhoseTurn { get; set; } + public WhichPerspective? PlayerInCheck { get; set; } + public WhichPerspective WhoseTurn { get; set; } } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs index aa489ea..5b70ad8 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs @@ -2,32 +2,37 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types { - public class Game - { - public string Player1 { get; set; } = string.Empty; - public string? Player2 { get; set; } = string.Empty; - public string GameName { get; set; } = string.Empty; - /// - /// Players[0] is the session owner, Players[1] is the other person. - /// - public IReadOnlyList Players - { - get - { - var list = new List(2) { Player1 }; - if (!string.IsNullOrEmpty(Player2)) list.Add(Player2); - return list; - } - } + public class Game + { + public string Player1 { get; set; } + public string? Player2 { get; set; } + public string GameName { get; set; } = string.Empty; - public Game() - { - } - public Game(string gameName, string player1, string? player2 = null) - { - GameName = gameName; - Player1 = player1; - Player2 = player2; - } - } + /// + /// Players[0] is the session owner, Players[1] is the other person. + /// + public IReadOnlyList Players + { + get + { + var list = new List(2) { Player1 }; + if (!string.IsNullOrEmpty(Player2)) list.Add(Player2); + return list; + } + } + + /// + /// Constructor for serialization. + /// + public Game() + { + } + + public Game(string gameName, string player1, string? player2 = null) + { + GameName = gameName; + Player1 = player1; + Player2 = player2; + } + } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs index 1c0ee78..8e28d04 100644 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs @@ -4,6 +4,6 @@ { public bool IsPromoted { get; set; } public WhichPiece WhichPiece { get; set; } - public WhichPlayer Owner { get; set; } + public WhichPerspective Owner { get; set; } } } diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs new file mode 100644 index 0000000..8e9a72a --- /dev/null +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs @@ -0,0 +1,9 @@ +namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types +{ + public class User + { + public string Id { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + } +} diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs new file mode 100644 index 0000000..cf8a4c4 --- /dev/null +++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs @@ -0,0 +1,9 @@ +namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types +{ + public enum WhichPerspective + { + Player1, + Player2, + Spectator + } +} diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPlayer.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPlayer.cs deleted file mode 100644 index 2ce7270..0000000 --- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPlayer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types -{ - public enum WhichPlayer - { - Player1, - Player2 - } -} diff --git a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs index e5ebad0..db5f8b9 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs @@ -16,226 +16,239 @@ using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Controllers { - [ApiController] - [Route("[controller]")] - [Authorize(Roles = "Shogi")] - public class GameController : ControllerBase - { - private readonly IGameboardManager gameboardManager; - private readonly IGameboardRepository gameboardRepository; - private readonly ISocketConnectionManager communicationManager; + [ApiController] + [Route("[controller]")] + [Authorize(Roles = "Shogi")] + public class GameController : ControllerBase + { + private readonly IGameboardManager gameboardManager; + private readonly IGameboardRepository gameboardRepository; + private readonly ISocketConnectionManager communicationManager; - public GameController( - IGameboardRepository repository, - IGameboardManager manager, - ISocketConnectionManager communicationManager) - { - gameboardManager = manager; - gameboardRepository = repository; - this.communicationManager = communicationManager; - } + public GameController( + IGameboardRepository repository, + IGameboardManager manager, + ISocketConnectionManager communicationManager) + { + gameboardManager = manager; + gameboardRepository = repository; + this.communicationManager = communicationManager; + } - [HttpPost("JoinCode")] - public async Task PostGameInvitation([FromBody] PostGameInvitation request) - { + [HttpPost("JoinCode")] + public async Task PostGameInvitation([FromBody] PostGameInvitation request) + { - //var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName); - //if (isPlayer1) - //{ - // var code = await gameboardRepository.PostJoinCode(request.SessionName, userName); - // return new CreatedResult("", new PostGameInvitationResponse(code)); - //} - //else - //{ - return new UnauthorizedResult(); - //} - } + //var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName); + //if (isPlayer1) + //{ + // var code = await gameboardRepository.PostJoinCode(request.SessionName, userName); + // return new CreatedResult("", new PostGameInvitationResponse(code)); + //} + //else + //{ + return new UnauthorizedResult(); + //} + } - [AllowAnonymous] - [HttpPost("GuestJoinCode")] - public async Task PostGuestGameInvitation([FromBody] PostGuestGameInvitation request) - { + [AllowAnonymous] + [HttpPost("GuestJoinCode")] + public async Task PostGuestGameInvitation([FromBody] PostGuestGameInvitation request) + { - //var isGuest = gameboardManager.IsGuest(request.GuestId); - //var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId); - //if (isGuest && await isPlayer1) - //{ - // var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId); - // return new CreatedResult("", new PostGameInvitationResponse(code)); - //} - //else - //{ - return new UnauthorizedResult(); - //} - } + //var isGuest = gameboardManager.IsGuest(request.GuestId); + //var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId); + //if (isGuest && await isPlayer1) + //{ + // var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId); + // return new CreatedResult("", new PostGameInvitationResponse(code)); + //} + //else + //{ + return new UnauthorizedResult(); + //} + } - [HttpPost("{gameName}/Move")] - public async Task PostMove([FromRoute] string gameName, [FromBody] PostMove request) - { - var user = await gameboardManager.ReadUser(User); - var session = await gameboardRepository.ReadSession(gameName); - if (session == null) - { - return NotFound(); - } - if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id)) - { - return Forbid("User is not seated at this game."); - } + [HttpPost("{gameName}/Move")] + public async Task PostMove([FromRoute] string gameName, [FromBody] PostMove request) + { + var user = await gameboardManager.ReadUser(User); + var session = await gameboardRepository.ReadSession(gameName); + if (session == null) + { + return NotFound(); + } + if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != 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); + 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) - { - 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); - return Ok(); - } - return Conflict("Illegal move."); - } + if (moveSuccess) + { + 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); + return Ok(); + } + return Conflict("Illegal move."); + } - // TODO: Use JWT tokens for guests so they can authenticate and use API routes, too. - //[Route("")] - //public async Task PostSession([FromBody] PostSession request) - //{ - // var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2); - // var success = await repository.CreateSession(model); - // if (success) - // { - // var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame) - // { - // Game = model.ToServiceModel(), - // PlayerName = - // } - // var task = request.IsPrivate - // ? communicationManager.BroadcastToPlayers(response, userName) - // : communicationManager.BroadcastToAll(response); - // return new CreatedResult("", null); - // } - // return new ConflictResult(); - //} + // TODO: Use JWT tokens for guests so they can authenticate and use API routes, too. + //[Route("")] + //public async Task PostSession([FromBody] PostSession request) + //{ + // var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2); + // var success = await repository.CreateSession(model); + // if (success) + // { + // var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame) + // { + // Game = model.ToServiceModel(), + // PlayerName = + // } + // var task = request.IsPrivate + // ? communicationManager.BroadcastToPlayers(response, userName) + // : communicationManager.BroadcastToAll(response); + // return new CreatedResult("", null); + // } + // return new ConflictResult(); + //} - [HttpPost] - 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); + [HttpPost] + 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) - { - await communicationManager.BroadcastToAll(new CreateGameResponse - { - Game = session.ToServiceModel(), - PlayerName = user.Id - }).ContinueWith(cont => - { - if (cont.Exception != null) - { - Console.Error.WriteLine("Yep"); - } - }); - return Ok(); - } - return Conflict(); + if (success) + { + try + { - } + await communicationManager.BroadcastToAll(new CreateGameResponse + { + Game = session.ToServiceModel(), + PlayerName = user.Id + }); + } + catch (Exception e) + { + Console.Error.WriteLine("Error broadcasting during PostSession"); + } - /// - /// Reads the board session and subscribes the caller to socket events for that session. - /// - [HttpGet("{gameName}")] - public async Task GetSession([FromRoute] string gameName) - { - var user = await ReadUserOrThrow(); - var session = await gameboardRepository.ReadSession(gameName); - if (session == null) - { - return NotFound(); - } + return Ok(); + } + return Conflict(); - communicationManager.SubscribeToGame(session, user!.Id); - var response = new GetSessionResponse() - { - Game = new Models.SessionMetadata(session).ToServiceModel(user), - BoardState = session.Shogi.ToServiceModel(), - MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(), - PlayerPerspective = user.Id == session.Player1.Id ? WhichPlayer.Player1 : WhichPlayer.Player2 - }; - return new JsonResult(response); - } + } - [HttpGet] - public async Task GetSessions() - { - var user = await ReadUserOrThrow(); - var sessions = await gameboardRepository.ReadSessionMetadatas(); + /// + /// Reads the board session and subscribes the caller to socket events for that session. + /// + [HttpGet("{gameName}")] + public async Task GetSession([FromRoute] string gameName) + { + var user = await ReadUserOrThrow(); + var session = await gameboardRepository.ReadSession(gameName); + if (session == null) + { + return NotFound(); + } - var sessionsJoinedByUser = sessions - .Where(s => s.IsSeated(user)) - .Select(s => s.ToServiceModel()) - .ToList(); - var sessionsNotJoinedByUser = sessions - .Where(s => !s.IsSeated(user)) - .Select(s => s.ToServiceModel()) - .ToList(); + var playerPerspective = WhichPerspective.Spectator; + if (session.Player1.Id == user.Id) + { + playerPerspective = WhichPerspective.Player1; + } + else if (session.Player2?.Id == user.Id) + { + playerPerspective = WhichPerspective.Player2; + } - return new GetSessionsResponse - { - PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser), - AllOtherSessions = new Collection(sessionsNotJoinedByUser) - }; - } + communicationManager.SubscribeToGame(session, user!.Id); + var response = new GetSessionResponse() + { + Game = new Models.SessionMetadata(session).ToServiceModel(), + BoardState = session.Shogi.ToServiceModel(), + MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(), + PlayerPerspective = playerPerspective + }; + return new JsonResult(response); + } - [HttpPut("{gameName}")] - public async Task PutJoinSession([FromRoute] string gameName) - { - var user = await ReadUserOrThrow(); - var session = await gameboardRepository.ReadSessionMetaData(gameName); - if (session == null) - { - return NotFound(); - } - if (session.Player2 != null) - { - return this.Conflict("This session already has two seated players and is full."); - } + [HttpGet] + public async Task GetSessions() + { + var user = await ReadUserOrThrow(); + var sessions = await gameboardRepository.ReadSessionMetadatas(); - session.SetPlayer2(user); - var success = await gameboardRepository.UpdateSession(session); - if (!success) return this.Problem(detail: "Unable to update session."); + var sessionsJoinedByUser = sessions + .Where(s => s.IsSeated(user)) + .Select(s => s.ToServiceModel()) + .ToList(); + var sessionsNotJoinedByUser = sessions + .Where(s => !s.IsSeated(user)) + .Select(s => s.ToServiceModel()) + .ToList(); - var opponentName = user.Id == session.Player1.Id - ? session.Player2!.Id - : session.Player1.Id; - await communicationManager.BroadcastToPlayers(new JoinGameResponse - { - GameName = session.Name, - PlayerName = user.Id - }, opponentName); - return Ok(); - } + return new GetSessionsResponse + { + PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser), + AllOtherSessions = new Collection(sessionsNotJoinedByUser) + }; + } - private async Task ReadUserOrThrow() - { - var user = await gameboardManager.ReadUser(User); - if (user == null) - { - throw new UnauthorizedAccessException("Unknown user claims."); - } - return user; - } - } + [HttpPut("{gameName}")] + public async Task PutJoinSession([FromRoute] string gameName) + { + var user = await ReadUserOrThrow(); + var session = await gameboardRepository.ReadSessionMetaData(gameName); + if (session == null) + { + return NotFound(); + } + if (session.Player2 != null) + { + 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."); + + var opponentName = user.Id == session.Player1.Id + ? session.Player2!.Id + : session.Player1.Id; + await communicationManager.BroadcastToPlayers(new JoinGameResponse + { + GameName = session.Name, + PlayerName = user.Id + }, opponentName); + return Ok(); + } + + private async Task ReadUserOrThrow() + { + var user = await gameboardManager.ReadUser(User); + if (user == null) + { + throw new UnauthorizedAccessException("Unknown user claims."); + } + return user; + } + } } diff --git a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs index 5b99eb3..6f442bd 100644 --- a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs +++ b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs @@ -15,89 +15,101 @@ using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Controllers { - [ApiController] - [Route("[controller]")] - [Authorize(Roles = "Shogi")] - public class SocketController : ControllerBase - { - private readonly ILogger logger; - private readonly ISocketTokenCache tokenCache; - private readonly IGameboardManager gameboardManager; - private readonly IGameboardRepository gameboardRepository; - private readonly AuthenticationProperties authenticationProps; + [ApiController] + [Route("[controller]")] + [Authorize(Roles = "Shogi")] + public class SocketController : ControllerBase + { + private readonly ILogger logger; + private readonly ISocketTokenCache tokenCache; + private readonly IGameboardManager gameboardManager; + private readonly IGameboardRepository gameboardRepository; + private readonly ISocketConnectionManager connectionManager; + private readonly AuthenticationProperties authenticationProps; - public SocketController( - ILogger logger, - ISocketTokenCache tokenCache, - IGameboardManager gameboardManager, - IGameboardRepository gameboardRepository) - { - this.logger = logger; - this.tokenCache = tokenCache; - this.gameboardManager = gameboardManager; - this.gameboardRepository = gameboardRepository; - authenticationProps = new AuthenticationProperties - { - AllowRefresh = true, - IsPersistent = true - }; - } + public SocketController( + ILogger logger, + ISocketTokenCache tokenCache, + IGameboardManager gameboardManager, + IGameboardRepository gameboardRepository, + ISocketConnectionManager connectionManager) + { + this.logger = logger; + this.tokenCache = tokenCache; + this.gameboardManager = gameboardManager; + this.gameboardRepository = gameboardRepository; + this.connectionManager = connectionManager; - [HttpGet("GuestLogout")] - [AllowAnonymous] - public async Task GuestLogout() - { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - return Ok(); - } + authenticationProps = new AuthenticationProperties + { + AllowRefresh = true, + IsPersistent = true + }; + } - [HttpGet("Token")] - public async Task GetToken() - { - var user = await gameboardManager.ReadUser(User); - if (user == null) - { - if (await gameboardManager.CreateUser(User)) - { - user = await gameboardManager.ReadUser(User); - } - } + [HttpGet("GuestLogout")] + [AllowAnonymous] + public async Task GuestLogout() + { + var signoutTask = HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + var userId = User?.UserId(); + if (!string.IsNullOrEmpty(userId)) + { + connectionManager.UnsubscribeFromBroadcastAndGames(userId); + } + + await signoutTask; + return Ok(); + } - if (user == null) - { - return Unauthorized(); - } + [HttpGet("Token")] + public async Task GetToken() + { + var user = await gameboardManager.ReadUser(User); + if (user == null) + { + if (await gameboardManager.CreateUser(User)) + { + user = await gameboardManager.ReadUser(User); + } + } - var token = tokenCache.GenerateToken(user.Id); - return new JsonResult(new GetTokenResponse(token)); - } + if (user == null) + { + return Unauthorized(); + } - [HttpGet("GuestToken")] - [AllowAnonymous] - public async Task GetGuestToken() - { - var user = await gameboardManager.ReadUser(User); - if (user == null) - { - // Create a guest user. - var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString()); - var success = await gameboardRepository.CreateUser(newUser); - if (!success) - { - return Conflict(); - } + var token = tokenCache.GenerateToken(user.Id); + return new JsonResult(new GetTokenResponse(token)); + } - var identity = newUser.CreateClaimsIdentity(); - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - new ClaimsPrincipal(identity), - authenticationProps - ); - user = newUser; - } + [HttpGet("GuestToken")] + [AllowAnonymous] + public async Task GetGuestToken() + { + var user = await gameboardManager.ReadUser(User); + if (user == null) + { + // Create a guest user. + var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString()); + var success = await gameboardRepository.CreateUser(newUser); + if (!success) + { + return Conflict(); + } - var token = tokenCache.GenerateToken(user.Id.ToString()); - return this.Ok(new GetGuestTokenResponse(user.Id, user.DisplayName, token)); - } - } + var identity = newUser.CreateClaimsIdentity(); + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(identity), + authenticationProps + ); + user = newUser; + } + + var token = tokenCache.GenerateToken(user.Id.ToString()); + return this.Ok(new GetGuestTokenResponse(user.Id, user.DisplayName, token)); + } + } } diff --git a/Gameboard.ShogiUI.Sockets/Extensions/LogMiddleware.cs b/Gameboard.ShogiUI.Sockets/Extensions/LogMiddleware.cs index 3c39341..d4860c4 100644 --- a/Gameboard.ShogiUI.Sockets/Extensions/LogMiddleware.cs +++ b/Gameboard.ShogiUI.Sockets/Extensions/LogMiddleware.cs @@ -1,43 +1,50 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using System.IO; +using System.Text; using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Extensions { - public class LogMiddleware - { - private readonly RequestDelegate next; - private readonly ILogger logger; + public class LogMiddleware + { + private readonly RequestDelegate next; + private readonly ILogger logger; - public LogMiddleware(RequestDelegate next, ILoggerFactory factory) - { - this.next = next; - logger = factory.CreateLogger(); - } - public async Task Invoke(HttpContext context) - { - try - { - await next(context); - } - finally - { - logger.LogInformation("Request {method} {url} => {statusCode}", - context.Request?.Method, - context.Request?.Path.Value, - context.Response?.StatusCode); - } - } - } + public LogMiddleware(RequestDelegate next, ILoggerFactory factory) + { + this.next = next; + logger = factory.CreateLogger(); + } - public static class IApplicationBuilderExtensions - { - public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) - { - builder.UseMiddleware(); - return builder; - } - } + public async Task Invoke(HttpContext context) + { + try + { + await next(context); + } + finally + { + using var stream = new MemoryStream(); + context.Request?.Body.CopyToAsync(stream); + + logger.LogInformation("Request {method} {url} => {statusCode} \n Body: {body}", + context.Request?.Method, + context.Request?.Path.Value, + context.Response?.StatusCode, + Encoding.UTF8.GetString(stream.ToArray())); + } + } + } + + public static class IApplicationBuilderExtensions + { + public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) + { + builder.UseMiddleware(); + return builder; + } + } } diff --git a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs b/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs index 3820ae7..d5333f4 100644 --- a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs +++ b/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs @@ -1,5 +1,4 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Types; -using System; using System.Text; using System.Text.RegularExpressions; @@ -21,7 +20,7 @@ namespace Gameboard.ShogiUI.Sockets.Extensions WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ", _ => " ? ", }; - if (self.Owner == WhichPlayer.Player2) + if (self.Owner == WhichPerspective.Player2) name = Regex.Replace(name, @"([^\s]+)\s", "$1."); return name; } diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs index 10fa790..0b2a5b8 100644 --- a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs +++ b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs @@ -11,151 +11,152 @@ using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Managers { - public interface ISocketConnectionManager - { - Task BroadcastToAll(IResponse response); - //Task BroadcastToGame(string gameName, IResponse response); - //Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2); - void SubscribeToGame(Session session, string playerName); - void SubscribeToBroadcast(WebSocket socket, string playerName); - void UnsubscribeFromBroadcastAndGames(string playerName); - void UnsubscribeFromGame(string gameName, string playerName); - Task BroadcastToPlayers(IResponse response, params string?[] playerNames); - } + public interface ISocketConnectionManager + { + Task BroadcastToAll(IResponse response); + //Task BroadcastToGame(string gameName, IResponse response); + //Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2); + void SubscribeToGame(Session session, string playerName); + void SubscribeToBroadcast(WebSocket socket, string playerName); + void UnsubscribeFromBroadcastAndGames(string playerName); + void UnsubscribeFromGame(string gameName, string playerName); + Task BroadcastToPlayers(IResponse response, params string?[] playerNames); + } - /// - /// Retains all active socket connections and provides convenient methods for sending messages to clients. - /// - public class SocketConnectionManager : ISocketConnectionManager - { - /// Dictionary key is player name. - private readonly ConcurrentDictionary connections; - /// Dictionary key is game name. - private readonly ConcurrentDictionary sessions; - private readonly ILogger logger; + /// + /// Retains all active socket connections and provides convenient methods for sending messages to clients. + /// + public class SocketConnectionManager : ISocketConnectionManager + { + /// Dictionary key is player name. + private readonly ConcurrentDictionary connections; + /// Dictionary key is game name. + private readonly ConcurrentDictionary sessions; + private readonly ILogger logger; - public SocketConnectionManager(ILogger logger) - { - this.logger = logger; - connections = new ConcurrentDictionary(); - sessions = new ConcurrentDictionary(); - } + public SocketConnectionManager(ILogger logger) + { + this.logger = logger; + connections = new ConcurrentDictionary(); + sessions = new ConcurrentDictionary(); + } - public void SubscribeToBroadcast(WebSocket socket, string playerName) - { - connections.TryAdd(playerName, socket); - } + public void SubscribeToBroadcast(WebSocket socket, string playerName) + { + connections.TryRemove(playerName, out var _); + connections.TryAdd(playerName, socket); + } - public void UnsubscribeFromBroadcastAndGames(string playerName) - { - connections.TryRemove(playerName, out _); - foreach (var kvp in sessions) - { - var sessionName = kvp.Key; - UnsubscribeFromGame(sessionName, playerName); - } - } + public void UnsubscribeFromBroadcastAndGames(string playerName) + { + connections.TryRemove(playerName, out _); + foreach (var kvp in sessions) + { + var sessionName = kvp.Key; + UnsubscribeFromGame(sessionName, playerName); + } + } - /// - /// Unsubscribes the player from their current game, then subscribes to the new game. - /// - public void SubscribeToGame(Session session, string playerName) - { - // Unsubscribe from any other games - foreach (var kvp in sessions) - { - var gameNameKey = kvp.Key; - UnsubscribeFromGame(gameNameKey, playerName); - } + /// + /// Unsubscribes the player from their current game, then subscribes to the new game. + /// + public void SubscribeToGame(Session session, string playerName) + { + // Unsubscribe from any other games + foreach (var kvp in sessions) + { + var gameNameKey = kvp.Key; + UnsubscribeFromGame(gameNameKey, playerName); + } - // Subscribe - if (connections.TryGetValue(playerName, out var socket)) - { - var s = sessions.GetOrAdd(session.Name, session); - s.Subscriptions.TryAdd(playerName, socket); - } - } + // Subscribe + if (connections.TryGetValue(playerName, out var socket)) + { + var s = sessions.GetOrAdd(session.Name, session); + s.Subscriptions.TryAdd(playerName, socket); + } + } - public void UnsubscribeFromGame(string gameName, string playerName) - { - if (sessions.TryGetValue(gameName, out var s)) - { - s.Subscriptions.TryRemove(playerName, out _); - if (s.Subscriptions.IsEmpty) sessions.TryRemove(gameName, out _); - } - } + public void UnsubscribeFromGame(string gameName, string playerName) + { + if (sessions.TryGetValue(gameName, out var s)) + { + s.Subscriptions.TryRemove(playerName, out _); + if (s.Subscriptions.IsEmpty) sessions.TryRemove(gameName, out _); + } + } - public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames) - { - var tasks = new List(playerNames.Length); - foreach (var name in playerNames) - { - if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket)) - { - var serialized = JsonConvert.SerializeObject(response); - logger.LogInformation("Response to {0} \n{1}\n", name, serialized); - tasks.Add(socket.SendTextAsync(serialized)); - } - } - await Task.WhenAll(tasks); - } - public Task BroadcastToAll(IResponse response) - { - var message = JsonConvert.SerializeObject(response); - logger.LogInformation($"Broadcasting\n{0}", message); - var tasks = new List(connections.Count); - foreach (var kvp in connections) - { - var socket = kvp.Value; - try - { + public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames) + { + var tasks = new List(playerNames.Length); + foreach (var name in playerNames) + { + if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket)) + { + var serialized = JsonConvert.SerializeObject(response); + logger.LogInformation("Response to {0} \n{1}\n", name, serialized); + tasks.Add(socket.SendTextAsync(serialized)); + } + } + await Task.WhenAll(tasks); + } + public Task BroadcastToAll(IResponse response) + { + var message = JsonConvert.SerializeObject(response); + logger.LogInformation($"Broadcasting\n{0}", message); + var tasks = new List(connections.Count); + foreach (var kvp in connections) + { + var socket = kvp.Value; + try + { - tasks.Add(socket.SendTextAsync(message)); - } - catch (WebSocketException webSocketException) - { - logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); - UnsubscribeFromBroadcastAndGames(kvp.Key); - } - catch (Exception exception) - { - logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); - UnsubscribeFromBroadcastAndGames(kvp.Key); - } - } - try - { - var task = Task.WhenAll(tasks); - return task; - } - catch (Exception e) - { - Console.WriteLine("Yo"); - } - return Task.FromResult(0); - } + tasks.Add(socket.SendTextAsync(message)); + } + catch (WebSocketException webSocketException) + { + logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); + UnsubscribeFromBroadcastAndGames(kvp.Key); + } + catch (Exception exception) + { + logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); + UnsubscribeFromBroadcastAndGames(kvp.Key); + } + } + try + { + var task = Task.WhenAll(tasks); + return task; + } + catch (Exception e) + { + Console.WriteLine("Yo"); + } + return Task.FromResult(0); + } - //public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2) - //{ - // if (sessions.TryGetValue(gameName, out var session)) - // { - // var serialized1 = JsonConvert.SerializeObject(forPlayer1); - // var serialized2 = JsonConvert.SerializeObject(forPlayer2); - // return Task.WhenAll( - // session.SendToPlayer1(serialized1), - // session.SendToPlayer2(serialized2)); - // } - // return Task.CompletedTask; - //} + //public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2) + //{ + // if (sessions.TryGetValue(gameName, out var session)) + // { + // var serialized1 = JsonConvert.SerializeObject(forPlayer1); + // var serialized2 = JsonConvert.SerializeObject(forPlayer2); + // return Task.WhenAll( + // session.SendToPlayer1(serialized1), + // session.SendToPlayer2(serialized2)); + // } + // return Task.CompletedTask; + //} - //public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers) - //{ - // if (sessions.TryGetValue(gameName, out var session)) - // { - // var serialized = JsonConvert.SerializeObject(messageForAllPlayers); - // return session.Broadcast(serialized); - // } - // return Task.CompletedTask; - //} - } + //public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers) + //{ + // if (sessions.TryGetValue(gameName, out var session)) + // { + // var serialized = JsonConvert.SerializeObject(messageForAllPlayers); + // return session.Broadcast(serialized); + // } + // return Task.CompletedTask; + //} + } } diff --git a/Gameboard.ShogiUI.Sockets/Models/Piece.cs b/Gameboard.ShogiUI.Sockets/Models/Piece.cs index 3c830e6..7b86808 100644 --- a/Gameboard.ShogiUI.Sockets/Models/Piece.cs +++ b/Gameboard.ShogiUI.Sockets/Models/Piece.cs @@ -8,11 +8,11 @@ namespace Gameboard.ShogiUI.Sockets.Models public class Piece : IPlanarElement { public WhichPiece WhichPiece { get; } - public WhichPlayer Owner { get; private set; } + public WhichPerspective Owner { get; private set; } public bool IsPromoted { get; private set; } - public bool IsUpsideDown => Owner == WhichPlayer.Player2; + public bool IsUpsideDown => Owner == WhichPerspective.Player2; - public Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) + public Piece(WhichPiece piece, WhichPerspective owner, bool isPromoted = false) { WhichPiece = piece; Owner = owner; @@ -28,9 +28,9 @@ namespace Gameboard.ShogiUI.Sockets.Models public void ToggleOwnership() { - Owner = Owner == WhichPlayer.Player1 - ? WhichPlayer.Player2 - : WhichPlayer.Player1; + Owner = Owner == WhichPerspective.Player1 + ? WhichPerspective.Player2 + : WhichPerspective.Player1; } public void Promote() => IsPromoted = CanPromote; diff --git a/Gameboard.ShogiUI.Sockets/Models/Session.cs b/Gameboard.ShogiUI.Sockets/Models/Session.cs index cd5c5dd..fd73b40 100644 --- a/Gameboard.ShogiUI.Sockets/Models/Session.cs +++ b/Gameboard.ShogiUI.Sockets/Models/Session.cs @@ -5,34 +5,34 @@ using System.Net.WebSockets; namespace Gameboard.ShogiUI.Sockets.Models { - public class Session - { - // TODO: Separate subscriptions to the Session from the Session. - [JsonIgnore] public ConcurrentDictionary Subscriptions { get; } - public string Name { get; } - public User Player1 { get; } - public User? Player2 { get; private set; } - public bool IsPrivate { get; } + public class Session + { + // TODO: Separate subscriptions to the Session from the Session. + [JsonIgnore] public ConcurrentDictionary Subscriptions { get; } + public string Name { get; } + public User Player1 { get; } + public User? Player2 { get; private set; } + public bool IsPrivate { get; } - // TODO: Don't retain the entire rules system within the Session model. It just needs the board state after rules are applied. - public Shogi Shogi { get; } + // TODO: Don't retain the entire rules system within the Session model. It just needs the board state after rules are applied. + public Shogi Shogi { get; } - public Session(string name, bool isPrivate, Shogi shogi, User player1, User? player2 = null) - { - Subscriptions = new ConcurrentDictionary(); + public Session(string name, bool isPrivate, Shogi shogi, User player1, User? player2 = null) + { + Subscriptions = new ConcurrentDictionary(); - Name = name; - Player1 = player1; - Player2 = player2; - IsPrivate = isPrivate; - Shogi = shogi; - } + Name = name; + Player1 = player1; + Player2 = player2; + IsPrivate = isPrivate; + Shogi = shogi; + } - public void SetPlayer2(User user) - { - Player2 = user; - } + public void SetPlayer2(User user) + { + Player2 = user; + } - public Game ToServiceModel() => new() { GameName = Name, Player1 = Player1.DisplayName, Player2 = Player2?.DisplayName }; - } + public Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName); + } } diff --git a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs b/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs index ac35d6f..350b273 100644 --- a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs +++ b/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs @@ -1,51 +1,37 @@ 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; } + /// + /// 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 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 void SetPlayer2(User user) + { + Player2 = user; + } - public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id; + public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id; - public ServiceModels.Types.Game ToServiceModel(User? user = null) - { - // TODO: Find a better way for the UI to know whether or not they are seated at a given game than client-side ID matching. - var player1 = Player1.DisplayName; - var player2 = Player2?.DisplayName; - if (user != null) - { - if (user.Id == Player1.Id) player1 = Player1.Id; - if (Player2 != null && user.Id == Player2.Id) - { - player2 = Player2.DisplayName; - } - } - return new(Name, player1, player2); - } - } + public ServiceModels.Types.Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName); + } } diff --git a/Gameboard.ShogiUI.Sockets/Models/Shogi.cs b/Gameboard.ShogiUI.Sockets/Models/Shogi.cs index 121b0d6..ec944a2 100644 --- a/Gameboard.ShogiUI.Sockets/Models/Shogi.cs +++ b/Gameboard.ShogiUI.Sockets/Models/Shogi.cs @@ -8,451 +8,456 @@ using System.Numerics; namespace Gameboard.ShogiUI.Sockets.Models { - /// - /// Facilitates Shogi board state transitions, cognisant of Shogi rules. - /// The board is always from Player1's perspective. - /// [0,0] is the lower-left position, [8,8] is the higher-right position - /// - public class Shogi - { - private delegate void MoveSetCallback(Piece piece, Vector2 position); - private readonly PathFinder2D pathFinder; - private Shogi? validationBoard; - private Vector2 player1King; - private Vector2 player2King; - private List Hand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand; - public List Player1Hand { get; } - public List Player2Hand { get; } - public CoordsToNotationCollection 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; } + /// + /// Facilitates Shogi board state transitions, cognisant of Shogi rules. + /// The board is always from Player1's perspective. + /// [0,0] is the lower-left position, [8,8] is the higher-right position + /// + public class Shogi + { + private delegate void MoveSetCallback(Piece piece, Vector2 position); + private readonly PathFinder2D pathFinder; + private Shogi? validationBoard; + private Vector2 player1King; + private Vector2 player2King; + private List Hand => WhoseTurn == WhichPerspective.Player1 ? Player1Hand : Player2Hand; + public List Player1Hand { get; } + public List Player2Hand { get; } + public CoordsToNotationCollection Board { get; } //TODO: Hide this being a getter method + public List MoveHistory { get; } + public WhichPerspective WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPerspective.Player1 : WhichPerspective.Player2; + public WhichPerspective? InCheck { get; private set; } + public bool IsCheckmate { get; private set; } - public string Error { get; private set; } + public string Error { get; private set; } - public Shogi() - { - Board = new CoordsToNotationCollection(); - MoveHistory = new List(20); - Player1Hand = new List(); - Player2Hand = new List(); - pathFinder = new PathFinder2D(Board, 9, 9); - player1King = new Vector2(4, 0); - player2King = new Vector2(4, 8); - Error = string.Empty; + public Shogi() + { + Board = new CoordsToNotationCollection(); + MoveHistory = new List(20); + Player1Hand = new List(); + Player2Hand = new List(); + pathFinder = new PathFinder2D(Board, 9, 9); + player1King = new Vector2(4, 0); + player2King = new Vector2(4, 8); + Error = string.Empty; - InitializeBoardState(); - } + InitializeBoardState(); + } - public Shogi(IList moves) : this() - { - for (var i = 0; i < moves.Count; i++) - { - 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}. {Error}"); - } - } - } + public Shogi(IList moves) : this() + { + for (var i = 0; i < moves.Count; i++) + { + 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}. {Error}"); + } + } + } - private Shogi(Shogi toCopy) - { - Board = new CoordsToNotationCollection(); - foreach (var kvp in toCopy.Board) - { - Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value); - } + private Shogi(Shogi toCopy) + { + Board = new CoordsToNotationCollection(); + foreach (var kvp in toCopy.Board) + { + Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value); + } - pathFinder = new PathFinder2D(Board, 9, 9); - MoveHistory = new List(toCopy.MoveHistory); - Player1Hand = new List(toCopy.Player1Hand); - Player2Hand = new List(toCopy.Player2Hand); - player1King = toCopy.player1King; - player2King = toCopy.player2King; - Error = toCopy.Error; - } + pathFinder = new PathFinder2D(Board, 9, 9); + MoveHistory = new List(toCopy.MoveHistory); + Player1Hand = new List(toCopy.Player1Hand); + Player2Hand = new List(toCopy.Player2Hand); + player1King = toCopy.player1King; + player2King = toCopy.player2King; + Error = toCopy.Error; + } - public bool Move(Move move) - { - var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; - var moveSuccess = TryMove(move); + public bool Move(Move move) + { + var otherPlayer = WhoseTurn == WhichPerspective.Player1 ? WhichPerspective.Player2 : WhichPerspective.Player1; + var moveSuccess = TryMove(move); - if (!moveSuccess) - { - return false; - } + if (!moveSuccess) + { + return false; + } - // Evaluate check - if (EvaluateCheckAfterMove(move, otherPlayer)) - { - InCheck = otherPlayer; - IsCheckmate = EvaluateCheckmate(); - } - return true; - } - /// - /// Attempts a given move. Returns false if the move is illegal. - /// - private bool TryMove(Move move) - { - // Try making the move in a "throw away" board. - if (validationBoard == null) - { - validationBoard = new Shogi(this); - } + // Evaluate check + if (EvaluateCheckAfterMove(move, otherPlayer)) + { + InCheck = otherPlayer; + IsCheckmate = EvaluateCheckmate(); + } + else + { + InCheck = null; + } + return true; + } + /// + /// Attempts a given move. Returns false if the move is illegal. + /// + private bool TryMove(Move move) + { + // Try making the move in a "throw away" board. + if (validationBoard == null) + { + validationBoard = new Shogi(this); + } - var isValid = move.PieceFromHand.HasValue - ? validationBoard.PlaceFromHand(move) - : validationBoard.PlaceFromBoard(move); - if (!isValid) - { - // Surface the error description. - Error = validationBoard.Error; - // Invalidate the "throw away" board. - validationBoard = null; - return false; - } - // If already in check, assert the move that resulted in check no longer results in check. - if (InCheck == WhoseTurn) - { - if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn)) - { - // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn; - return false; - } - } + var isValid = move.PieceFromHand.HasValue + ? validationBoard.PlaceFromHand(move) + : validationBoard.PlaceFromBoard(move); + if (!isValid) + { + // Surface the error description. + Error = validationBoard.Error; + // Invalidate the "throw away" board. + validationBoard = null; + return false; + } + // If already in check, assert the move that resulted in check no longer results in check. + if (InCheck == WhoseTurn) + { + if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn)) + { + // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn; + return false; + } + } - // The move is valid and legal; update board state. - if (move.PieceFromHand.HasValue) PlaceFromHand(move); - else PlaceFromBoard(move); - return true; - } - /// True if the move was successful. - private bool PlaceFromHand(Move move) - { - var index = Hand.FindIndex(p => p.WhichPiece == move.PieceFromHand); - if (index < 0) - { - Error = $"{move.PieceFromHand} does not exist in the hand."; - return false; - } - if (Board[move.To] != null) - { - Error = $"Illegal move - attempting to capture while playing a piece from the hand."; - return false; - } + // The move is valid and legal; update board state. + if (move.PieceFromHand.HasValue) PlaceFromHand(move); + else PlaceFromBoard(move); + return true; + } + /// True if the move was successful. + private bool PlaceFromHand(Move move) + { + var index = Hand.FindIndex(p => p.WhichPiece == move.PieceFromHand); + if (index < 0) + { + Error = $"{move.PieceFromHand} does not exist in the hand."; + return false; + } + if (Board[move.To] != null) + { + Error = $"Illegal move - attempting to capture while playing a piece from the hand."; + return false; + } - switch (move.PieceFromHand!.Value) - { - case WhichPiece.Knight: - { - // Knight cannot be placed onto the farthest two ranks from the hand. - if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y > 6) - || (WhoseTurn == WhichPlayer.Player2 && move.To.Y < 2)) - { - Error = $"Knight has no valid moves after placed."; - return false; - } - break; - } - case WhichPiece.Lance: - case WhichPiece.Pawn: - { - // Lance and Pawn cannot be placed onto the farthest rank from the hand. - if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y == 8) - || (WhoseTurn == WhichPlayer.Player2 && move.To.Y == 0)) - { - Error = $"{move.PieceFromHand} has no valid moves after placed."; - return false; - } - break; - } - } + switch (move.PieceFromHand!.Value) + { + case WhichPiece.Knight: + { + // Knight cannot be placed onto the farthest two ranks from the hand. + if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y > 6) + || (WhoseTurn == WhichPerspective.Player2 && move.To.Y < 2)) + { + Error = $"Knight has no valid moves after placed."; + return false; + } + break; + } + case WhichPiece.Lance: + case WhichPiece.Pawn: + { + // Lance and Pawn cannot be placed onto the farthest rank from the hand. + if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y == 8) + || (WhoseTurn == WhichPerspective.Player2 && move.To.Y == 0)) + { + Error = $"{move.PieceFromHand} has no valid moves after placed."; + return false; + } + break; + } + } - // Mutate the board. - Board[move.To] = Hand[index]; - Hand.RemoveAt(index); + // Mutate the board. + Board[move.To] = Hand[index]; + Hand.RemoveAt(index); + MoveHistory.Add(move); - return true; - } - /// True if the move was successful. - private bool PlaceFromBoard(Move move) - { - var fromPiece = Board[move.From!.Value]; - 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.Value, 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. - } + return true; + } + /// True if the move was successful. + private bool PlaceFromBoard(Move move) + { + var fromPiece = Board[move.From!.Value]; + 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.Value, 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]; - if (captured != null) - { - if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece. - captured.Capture(); - Hand.Add(captured); - } + var captured = Board[move.To]; + if (captured != null) + { + if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece. + captured.Capture(); + Hand.Add(captured); + } - //Mutate the board. - if (move.IsPromotion) - { - if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5)) - { - fromPiece.Promote(); - } - else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3)) - { - fromPiece.Promote(); - } - } - Board[move.To] = fromPiece; - Board[move.From!.Value] = null; - if (fromPiece.WhichPiece == WhichPiece.King) - { - if (fromPiece.Owner == WhichPlayer.Player1) - { - player1King.X = move.To.X; - player1King.Y = move.To.Y; - } - else if (fromPiece.Owner == WhichPlayer.Player2) - { - player2King.X = move.To.X; - player2King.Y = move.To.Y; - } - } - MoveHistory.Add(move); - return true; - } + //Mutate the board. + if (move.IsPromotion) + { + if (WhoseTurn == WhichPerspective.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5)) + { + fromPiece.Promote(); + } + else if (WhoseTurn == WhichPerspective.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3)) + { + fromPiece.Promote(); + } + } + Board[move.To] = fromPiece; + Board[move.From!.Value] = null; + if (fromPiece.WhichPiece == WhichPiece.King) + { + if (fromPiece.Owner == WhichPerspective.Player1) + { + player1King.X = move.To.X; + player1King.Y = move.To.Y; + } + else if (fromPiece.Owner == WhichPerspective.Player2) + { + player2King.X = move.To.X; + player2King.Y = move.To.Y; + } + } + MoveHistory.Add(move); + return true; + } - private bool IsPathable(Vector2 from, Vector2 to) - { - var piece = Board[from]; - if (piece == null) return false; + private bool IsPathable(Vector2 from, Vector2 to) + { + var piece = Board[from]; + if (piece == null) return false; - var isObstructed = false; - var isPathable = pathFinder.PathTo(from, to, (other, position) => - { - if (other.Owner == piece.Owner) isObstructed = true; - }); - return !isObstructed && isPathable; - } + var isObstructed = false; + var isPathable = pathFinder.PathTo(from, to, (other, position) => + { + if (other.Owner == piece.Owner) isObstructed = true; + }); + return !isObstructed && isPathable; + } - #region Rules Validation - private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer) - { - if (whichPlayer == InCheck) return true; // If we already know the player is in check, don't bother. + #region Rules Validation + private bool EvaluateCheckAfterMove(Move move, WhichPerspective WhichPerspective) + { + if (WhichPerspective == InCheck) return true; // If we already know the player is in check, don't bother. - var isCheck = false; - var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King; + var isCheck = false; + var kingPosition = WhichPerspective == WhichPerspective.Player1 ? player1King : player2King; - // Check if the move put the king in check. - if (pathFinder.PathTo(move.To, kingPosition)) return true; + // Check if the move put the king in check. + if (pathFinder.PathTo(move.To, kingPosition)) return true; - if (move.From.HasValue) - { - // Get line equation from king through the now-unoccupied location. - var direction = Vector2.Subtract(kingPosition, move.From!.Value); - var slope = Math.Abs(direction.Y / direction.X); - // If absolute slope is 45°, look for a bishop along the line. - // If absolute slope is 0° or 90°, look for a rook along the line. - // if absolute slope is 0°, look for lance along the line. - if (float.IsInfinity(slope)) - { - // if slope of the move is also infinity...can skip this? - pathFinder.LinePathTo(kingPosition, direction, (piece, position) => - { - if (piece.Owner != whichPlayer) - { - switch (piece.WhichPiece) - { - case WhichPiece.Rook: - isCheck = true; - break; - case WhichPiece.Lance: - if (!piece.IsPromoted) isCheck = true; - break; - } - } - }); - } - else if (slope == 1) - { - pathFinder.LinePathTo(kingPosition, direction, (piece, position) => - { - if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Bishop) - { - isCheck = true; - } - }); - } - else if (slope == 0) - { - pathFinder.LinePathTo(kingPosition, direction, (piece, position) => - { - if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Rook) - { - isCheck = true; - } - }); - } - } - else - { - // TODO: Check for illegal move from hand. It is illegal to place from the hand such that you check-mate your opponent. - // Go read the shogi rules to be sure this is true. - } + if (move.From.HasValue) + { + // Get line equation from king through the now-unoccupied location. + var direction = Vector2.Subtract(kingPosition, move.From!.Value); + var slope = Math.Abs(direction.Y / direction.X); + // If absolute slope is 45°, look for a bishop along the line. + // If absolute slope is 0° or 90°, look for a rook along the line. + // if absolute slope is 0°, look for lance along the line. + if (float.IsInfinity(slope)) + { + // if slope of the move is also infinity...can skip this? + pathFinder.LinePathTo(kingPosition, direction, (piece, position) => + { + if (piece.Owner != WhichPerspective) + { + switch (piece.WhichPiece) + { + case WhichPiece.Rook: + isCheck = true; + break; + case WhichPiece.Lance: + if (!piece.IsPromoted) isCheck = true; + break; + } + } + }); + } + else if (slope == 1) + { + pathFinder.LinePathTo(kingPosition, direction, (piece, position) => + { + if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Bishop) + { + isCheck = true; + } + }); + } + else if (slope == 0) + { + pathFinder.LinePathTo(kingPosition, direction, (piece, position) => + { + if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Rook) + { + isCheck = true; + } + }); + } + } + else + { + // TODO: Check for illegal move from hand. It is illegal to place from the hand such that you check-mate your opponent. + // Go read the shogi rules to be sure this is true. + } - return isCheck; - } - private bool EvaluateCheckmate() - { - if (!InCheck.HasValue) return false; + return isCheck; + } + private bool EvaluateCheckmate() + { + if (!InCheck.HasValue) return false; - // Assume true and try to disprove. - var isCheckmate = true; - Board.ForEachNotNull((piece, from) => // For each piece... - { - // Short circuit - if (!isCheckmate) return; + // Assume true and try to disprove. + var isCheckmate = true; + Board.ForEachNotNull((piece, from) => // For each piece... + { + // Short circuit + if (!isCheckmate) return; - if (piece.Owner == InCheck) // ...owned by the player in check... - { - // ...evaluate if any move gets the player out of check. - pathFinder.PathEvery(from, (other, position) => - { - if (validationBoard == null) validationBoard = new Shogi(this); - var moveToTry = new Move(from, position); - var moveSuccess = validationBoard.TryMove(moveToTry); - if (moveSuccess) - { - validationBoard = null; - if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value)) - { - isCheckmate = false; - } - } - }); - } - }); - return isCheckmate; - } - #endregion + if (piece.Owner == InCheck) // ...owned by the player in check... + { + // ...evaluate if any move gets the player out of check. + pathFinder.PathEvery(from, (other, position) => + { + if (validationBoard == null) validationBoard = new Shogi(this); + var moveToTry = new Move(from, position); + var moveSuccess = validationBoard.TryMove(moveToTry); + if (moveSuccess) + { + validationBoard = null; + if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value)) + { + isCheckmate = false; + } + } + }); + } + }); + return isCheckmate; + } + #endregion - private void InitializeBoardState() - { - Board["A1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1); - Board["B1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1); - Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1); - Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1); - Board["E1"] = new Piece(WhichPiece.King, WhichPlayer.Player1); - Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1); - Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1); - Board["H1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1); - Board["I1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1); + private void InitializeBoardState() + { + Board["A1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1); + Board["B1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1); + Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1); + Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1); + Board["E1"] = new Piece(WhichPiece.King, WhichPerspective.Player1); + Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1); + Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1); + Board["H1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1); + Board["I1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1); - Board["A2"] = null; - Board["B2"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player1); - Board["C2"] = null; - Board["D2"] = null; - Board["E2"] = null; - Board["F2"] = null; - Board["G2"] = null; - Board["H2"] = new Piece(WhichPiece.Rook, WhichPlayer.Player1); - Board["I2"] = null; + Board["A2"] = null; + Board["B2"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player1); + Board["C2"] = null; + Board["D2"] = null; + Board["E2"] = null; + Board["F2"] = null; + Board["G2"] = null; + Board["H2"] = new Piece(WhichPiece.Rook, WhichPerspective.Player1); + Board["I2"] = null; - Board["A3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["B3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["C3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["D3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["E3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["F3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["G3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["H3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); - Board["I3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1); + Board["A3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["B3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["C3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["D3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["E3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["F3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["G3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["H3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); + Board["I3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1); - Board["A4"] = null; - Board["B4"] = null; - Board["C4"] = null; - Board["D4"] = null; - Board["E4"] = null; - Board["F4"] = null; - Board["G4"] = null; - Board["H4"] = null; - Board["I4"] = null; + Board["A4"] = null; + Board["B4"] = null; + Board["C4"] = null; + Board["D4"] = null; + Board["E4"] = null; + Board["F4"] = null; + Board["G4"] = null; + Board["H4"] = null; + Board["I4"] = null; - Board["A5"] = null; - Board["B5"] = null; - Board["C5"] = null; - Board["D5"] = null; - Board["E5"] = null; - Board["F5"] = null; - Board["G5"] = null; - Board["H5"] = null; - Board["I5"] = null; + Board["A5"] = null; + Board["B5"] = null; + Board["C5"] = null; + Board["D5"] = null; + Board["E5"] = null; + Board["F5"] = null; + Board["G5"] = null; + Board["H5"] = null; + Board["I5"] = null; - Board["A6"] = null; - Board["B6"] = null; - Board["C6"] = null; - Board["D6"] = null; - Board["E6"] = null; - Board["F6"] = null; - Board["G6"] = null; - Board["H6"] = null; - Board["I6"] = null; + Board["A6"] = null; + Board["B6"] = null; + Board["C6"] = null; + Board["D6"] = null; + Board["E6"] = null; + Board["F6"] = null; + Board["G6"] = null; + Board["H6"] = null; + Board["I6"] = null; - Board["A7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["B7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["C7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["D7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["E7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["F7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["G7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["H7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); - Board["I7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2); + Board["A7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["B7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["C7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["D7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["E7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["F7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["G7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["H7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); + Board["I7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2); - Board["A8"] = null; - Board["B8"] = new Piece(WhichPiece.Rook, WhichPlayer.Player2); - Board["C8"] = null; - Board["D8"] = null; - Board["E8"] = null; - Board["F8"] = null; - Board["G8"] = null; - Board["H8"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player2); - Board["I8"] = null; + Board["A8"] = null; + Board["B8"] = new Piece(WhichPiece.Rook, WhichPerspective.Player2); + Board["C8"] = null; + Board["D8"] = null; + Board["E8"] = null; + Board["F8"] = null; + Board["G8"] = null; + Board["H8"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player2); + Board["I8"] = null; - Board["A9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2); - Board["B9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2); - Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2); - Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2); - Board["E9"] = new Piece(WhichPiece.King, WhichPlayer.Player2); - Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2); - Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2); - Board["H9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2); - Board["I9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2); - } + Board["A9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2); + Board["B9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2); + Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2); + Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2); + Board["E9"] = new Piece(WhichPiece.King, WhichPerspective.Player2); + Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2); + Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2); + Board["H9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2); + Board["I9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2); + } - public BoardState ToServiceModel() - { - return new BoardState - { - Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()), - PlayerInCheck = InCheck, - WhoseTurn = WhoseTurn, - Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(), - Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList() - }; - } - } + public BoardState ToServiceModel() + { + return new BoardState + { + Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()), + PlayerInCheck = InCheck, + WhoseTurn = WhoseTurn, + Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(), + Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList() + }; + } + } } diff --git a/Gameboard.ShogiUI.Sockets/Models/User.cs b/Gameboard.ShogiUI.Sockets/Models/User.cs index 60f5b2b..7c1a2fa 100644 --- a/Gameboard.ShogiUI.Sockets/Models/User.cs +++ b/Gameboard.ShogiUI.Sockets/Models/User.cs @@ -8,72 +8,78 @@ using System.Security.Claims; namespace Gameboard.ShogiUI.Sockets.Models { - public class User - { - public static readonly ReadOnlyCollection Adjectives = new(new[] { - "Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly" - }); - public static readonly ReadOnlyCollection Subjects = new(new[] { - "Hippo", "Basil", "Mouse", "Walnut", "Prince", "Lima Bean", "Coala", "Potato" - }); - public static User CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft); - public static User CreateGuestUser(string id) - { - var random = new Random(); - // Adjective - var index = (int)Math.Floor(random.NextDouble() * Adjectives.Count); - var adj = Adjectives[index]; - // Subject - index = (int)Math.Floor(random.NextDouble() * Subjects.Count); - var subj = Subjects[index]; + public class User + { + public static readonly ReadOnlyCollection Adjectives = new(new[] { + "Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly" + }); + public static readonly ReadOnlyCollection Subjects = new(new[] { + "Hippo", "Basil", "Mouse", "Walnut", "Prince", "Lima Bean", "Coala", "Potato", "Penguin" + }); + public static User CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft); + public static User CreateGuestUser(string id) + { + var random = new Random(); + // Adjective + var index = (int)Math.Floor(random.NextDouble() * Adjectives.Count); + var adj = Adjectives[index]; + // Subject + index = (int)Math.Floor(random.NextDouble() * Subjects.Count); + var subj = Subjects[index]; - return new User(id, $"{adj} {subj}", WhichLoginPlatform.Guest); - } + return new User(id, $"{adj} {subj}", WhichLoginPlatform.Guest); + } - public string Id { get; } - public string DisplayName { get; } + public string Id { get; } + public string DisplayName { get; } - public WhichLoginPlatform LoginPlatform { get; } + public WhichLoginPlatform LoginPlatform { get; } - public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest; + public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest; - public User(string id, string displayName, WhichLoginPlatform platform) - { - Id = id; - DisplayName = displayName; - LoginPlatform = platform; - } + public User(string id, string displayName, WhichLoginPlatform platform) + { + Id = id; + DisplayName = displayName; + LoginPlatform = platform; + } - public User(UserDocument document) - { - Id = document.Id; - DisplayName = document.DisplayName; - LoginPlatform = document.Platform; - } + public User(UserDocument document) + { + Id = document.Id; + DisplayName = document.DisplayName; + LoginPlatform = document.Platform; + } - public ClaimsIdentity CreateClaimsIdentity() - { - if (LoginPlatform == WhichLoginPlatform.Guest) - { - var claims = new List(4) - { - new Claim(ClaimTypes.NameIdentifier, Id), - new Claim(ClaimTypes.Name, DisplayName), - new Claim(ClaimTypes.Role, "Guest"), - new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers. + public ClaimsIdentity CreateClaimsIdentity() + { + if (LoginPlatform == WhichLoginPlatform.Guest) + { + var claims = new List(4) + { + new Claim(ClaimTypes.NameIdentifier, Id), + new Claim(ClaimTypes.Name, DisplayName), + new Claim(ClaimTypes.Role, "Guest"), + new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers. }; - return new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - } - else - { - var claims = new List(3) - { - new Claim(ClaimTypes.NameIdentifier, Id), - new Claim(ClaimTypes.Name, DisplayName), - new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers. + return new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + } + else + { + var claims = new List(3) + { + new Claim(ClaimTypes.NameIdentifier, Id), + new Claim(ClaimTypes.Name, DisplayName), + new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers. }; - return new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme); - } - } - } + return new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme); + } + } + + public ServiceModels.Types.User ToServiceModel() => new() + { + Id = Id, + Name = DisplayName + }; + } } diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs index 3f28f87..7f0be6f 100644 --- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs +++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs @@ -5,7 +5,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels public class Piece { public bool IsPromoted { get; set; } - public WhichPlayer Owner { get; set; } + public WhichPerspective Owner { get; set; } public WhichPiece WhichPiece { get; set; } /// diff --git a/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs b/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs index a04a25b..fc00288 100644 --- a/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs +++ b/Gameboard.ShogiUI.Sockets/ShogiUserClaimsTransformer.cs @@ -1,8 +1,5 @@ using Gameboard.ShogiUI.Sockets.Repositories; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using System; -using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; diff --git a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs index 59f8695..1476dbb 100644 --- a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs +++ b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs @@ -3,7 +3,7 @@ using Gameboard.ShogiUI.Sockets.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using System.Numerics; -using WhichPlayer = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer; +using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPerspective; using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece; namespace Gameboard.ShogiUI.UnitTests.Rules { diff --git a/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs b/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs index 37234f1..9c565f6 100644 --- a/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs +++ b/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs @@ -6,340 +6,366 @@ using System.Linq; using Xunit; using Xunit.Abstractions; using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece; -using WhichPlayer = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer; +using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPerspective; namespace Gameboard.ShogiUI.xUnitTests { - public class ShogiShould - { - private readonly ITestOutputHelper output; - public ShogiShould(ITestOutputHelper output) - { - this.output = output; - } + public class ShogiShould + { + private readonly ITestOutputHelper output; + public ShogiShould(ITestOutputHelper output) + { + this.output = output; + } - [Fact] - public void InitializeBoardState() - { - // Act - var board = new Shogi().Board; + [Fact] + public void InitializeBoardState() + { + // Act + var board = new Shogi().Board; - // Assert - board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); - board["A1"].Owner.Should().Be(WhichPlayer.Player1); - board["A1"].IsPromoted.Should().Be(false); - board["B1"].WhichPiece.Should().Be(WhichPiece.Knight); - board["B1"].Owner.Should().Be(WhichPlayer.Player1); - board["B1"].IsPromoted.Should().Be(false); - board["C1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); - board["C1"].Owner.Should().Be(WhichPlayer.Player1); - board["C1"].IsPromoted.Should().Be(false); - board["D1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); - board["D1"].Owner.Should().Be(WhichPlayer.Player1); - board["D1"].IsPromoted.Should().Be(false); - board["E1"].WhichPiece.Should().Be(WhichPiece.King); - board["E1"].Owner.Should().Be(WhichPlayer.Player1); - board["E1"].IsPromoted.Should().Be(false); - board["F1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); - board["F1"].Owner.Should().Be(WhichPlayer.Player1); - board["F1"].IsPromoted.Should().Be(false); - board["G1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); - board["G1"].Owner.Should().Be(WhichPlayer.Player1); - board["G1"].IsPromoted.Should().Be(false); - board["H1"].WhichPiece.Should().Be(WhichPiece.Knight); - board["H1"].Owner.Should().Be(WhichPlayer.Player1); - board["H1"].IsPromoted.Should().Be(false); - board["I1"].WhichPiece.Should().Be(WhichPiece.Lance); - board["I1"].Owner.Should().Be(WhichPlayer.Player1); - board["I1"].IsPromoted.Should().Be(false); + // Assert + board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); + board["A1"].Owner.Should().Be(WhichPerspective.Player1); + board["A1"].IsPromoted.Should().Be(false); + board["B1"].WhichPiece.Should().Be(WhichPiece.Knight); + board["B1"].Owner.Should().Be(WhichPerspective.Player1); + board["B1"].IsPromoted.Should().Be(false); + board["C1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); + board["C1"].Owner.Should().Be(WhichPerspective.Player1); + board["C1"].IsPromoted.Should().Be(false); + board["D1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); + board["D1"].Owner.Should().Be(WhichPerspective.Player1); + board["D1"].IsPromoted.Should().Be(false); + board["E1"].WhichPiece.Should().Be(WhichPiece.King); + board["E1"].Owner.Should().Be(WhichPerspective.Player1); + board["E1"].IsPromoted.Should().Be(false); + board["F1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); + board["F1"].Owner.Should().Be(WhichPerspective.Player1); + board["F1"].IsPromoted.Should().Be(false); + board["G1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); + board["G1"].Owner.Should().Be(WhichPerspective.Player1); + board["G1"].IsPromoted.Should().Be(false); + board["H1"].WhichPiece.Should().Be(WhichPiece.Knight); + board["H1"].Owner.Should().Be(WhichPerspective.Player1); + board["H1"].IsPromoted.Should().Be(false); + board["I1"].WhichPiece.Should().Be(WhichPiece.Lance); + board["I1"].Owner.Should().Be(WhichPerspective.Player1); + board["I1"].IsPromoted.Should().Be(false); - board["A2"].Should().BeNull(); - board["B2"].WhichPiece.Should().Be(WhichPiece.Bishop); - board["B2"].Owner.Should().Be(WhichPlayer.Player1); - board["B2"].IsPromoted.Should().Be(false); - board["C2"].Should().BeNull(); - board["D2"].Should().BeNull(); - board["E2"].Should().BeNull(); - board["F2"].Should().BeNull(); - board["G2"].Should().BeNull(); - board["H2"].WhichPiece.Should().Be(WhichPiece.Rook); - board["H2"].Owner.Should().Be(WhichPlayer.Player1); - board["H2"].IsPromoted.Should().Be(false); - board["I2"].Should().BeNull(); + board["A2"].Should().BeNull(); + board["B2"].WhichPiece.Should().Be(WhichPiece.Bishop); + board["B2"].Owner.Should().Be(WhichPerspective.Player1); + board["B2"].IsPromoted.Should().Be(false); + board["C2"].Should().BeNull(); + board["D2"].Should().BeNull(); + board["E2"].Should().BeNull(); + board["F2"].Should().BeNull(); + board["G2"].Should().BeNull(); + board["H2"].WhichPiece.Should().Be(WhichPiece.Rook); + board["H2"].Owner.Should().Be(WhichPerspective.Player1); + board["H2"].IsPromoted.Should().Be(false); + board["I2"].Should().BeNull(); - board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["A3"].Owner.Should().Be(WhichPlayer.Player1); - board["A3"].IsPromoted.Should().Be(false); - board["B3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["B3"].Owner.Should().Be(WhichPlayer.Player1); - board["B3"].IsPromoted.Should().Be(false); - board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["C3"].Owner.Should().Be(WhichPlayer.Player1); - board["C3"].IsPromoted.Should().Be(false); - board["D3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["D3"].Owner.Should().Be(WhichPlayer.Player1); - board["D3"].IsPromoted.Should().Be(false); - board["E3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["E3"].Owner.Should().Be(WhichPlayer.Player1); - board["E3"].IsPromoted.Should().Be(false); - board["F3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["F3"].Owner.Should().Be(WhichPlayer.Player1); - board["F3"].IsPromoted.Should().Be(false); - board["G3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["G3"].Owner.Should().Be(WhichPlayer.Player1); - board["G3"].IsPromoted.Should().Be(false); - board["H3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["H3"].Owner.Should().Be(WhichPlayer.Player1); - board["H3"].IsPromoted.Should().Be(false); - board["I3"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["I3"].Owner.Should().Be(WhichPlayer.Player1); - board["I3"].IsPromoted.Should().Be(false); + board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["A3"].Owner.Should().Be(WhichPerspective.Player1); + board["A3"].IsPromoted.Should().Be(false); + board["B3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["B3"].Owner.Should().Be(WhichPerspective.Player1); + board["B3"].IsPromoted.Should().Be(false); + board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["C3"].Owner.Should().Be(WhichPerspective.Player1); + board["C3"].IsPromoted.Should().Be(false); + board["D3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["D3"].Owner.Should().Be(WhichPerspective.Player1); + board["D3"].IsPromoted.Should().Be(false); + board["E3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["E3"].Owner.Should().Be(WhichPerspective.Player1); + board["E3"].IsPromoted.Should().Be(false); + board["F3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["F3"].Owner.Should().Be(WhichPerspective.Player1); + board["F3"].IsPromoted.Should().Be(false); + board["G3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["G3"].Owner.Should().Be(WhichPerspective.Player1); + board["G3"].IsPromoted.Should().Be(false); + board["H3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["H3"].Owner.Should().Be(WhichPerspective.Player1); + board["H3"].IsPromoted.Should().Be(false); + board["I3"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["I3"].Owner.Should().Be(WhichPerspective.Player1); + board["I3"].IsPromoted.Should().Be(false); - board["A4"].Should().BeNull(); - board["B4"].Should().BeNull(); - board["C4"].Should().BeNull(); - board["D4"].Should().BeNull(); - board["E4"].Should().BeNull(); - board["F4"].Should().BeNull(); - board["G4"].Should().BeNull(); - board["H4"].Should().BeNull(); - board["I4"].Should().BeNull(); + board["A4"].Should().BeNull(); + board["B4"].Should().BeNull(); + board["C4"].Should().BeNull(); + board["D4"].Should().BeNull(); + board["E4"].Should().BeNull(); + board["F4"].Should().BeNull(); + board["G4"].Should().BeNull(); + board["H4"].Should().BeNull(); + board["I4"].Should().BeNull(); - board["A5"].Should().BeNull(); - board["B5"].Should().BeNull(); - board["C5"].Should().BeNull(); - board["D5"].Should().BeNull(); - board["E5"].Should().BeNull(); - board["F5"].Should().BeNull(); - board["G5"].Should().BeNull(); - board["H5"].Should().BeNull(); - board["I5"].Should().BeNull(); + board["A5"].Should().BeNull(); + board["B5"].Should().BeNull(); + board["C5"].Should().BeNull(); + board["D5"].Should().BeNull(); + board["E5"].Should().BeNull(); + board["F5"].Should().BeNull(); + board["G5"].Should().BeNull(); + board["H5"].Should().BeNull(); + board["I5"].Should().BeNull(); - board["A6"].Should().BeNull(); - board["B6"].Should().BeNull(); - board["C6"].Should().BeNull(); - board["D6"].Should().BeNull(); - board["E6"].Should().BeNull(); - board["F6"].Should().BeNull(); - board["G6"].Should().BeNull(); - board["H6"].Should().BeNull(); - board["I6"].Should().BeNull(); + board["A6"].Should().BeNull(); + board["B6"].Should().BeNull(); + board["C6"].Should().BeNull(); + board["D6"].Should().BeNull(); + board["E6"].Should().BeNull(); + board["F6"].Should().BeNull(); + board["G6"].Should().BeNull(); + board["H6"].Should().BeNull(); + board["I6"].Should().BeNull(); - board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["A7"].Owner.Should().Be(WhichPlayer.Player2); - board["A7"].IsPromoted.Should().Be(false); - board["B7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["B7"].Owner.Should().Be(WhichPlayer.Player2); - board["B7"].IsPromoted.Should().Be(false); - board["C7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["C7"].Owner.Should().Be(WhichPlayer.Player2); - board["C7"].IsPromoted.Should().Be(false); - board["D7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["D7"].Owner.Should().Be(WhichPlayer.Player2); - board["D7"].IsPromoted.Should().Be(false); - board["E7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["E7"].Owner.Should().Be(WhichPlayer.Player2); - board["E7"].IsPromoted.Should().Be(false); - board["F7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["F7"].Owner.Should().Be(WhichPlayer.Player2); - board["F7"].IsPromoted.Should().Be(false); - board["G7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["G7"].Owner.Should().Be(WhichPlayer.Player2); - board["G7"].IsPromoted.Should().Be(false); - board["H7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["H7"].Owner.Should().Be(WhichPlayer.Player2); - board["H7"].IsPromoted.Should().Be(false); - board["I7"].WhichPiece.Should().Be(WhichPiece.Pawn); - board["I7"].Owner.Should().Be(WhichPlayer.Player2); - board["I7"].IsPromoted.Should().Be(false); + board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["A7"].Owner.Should().Be(WhichPerspective.Player2); + board["A7"].IsPromoted.Should().Be(false); + board["B7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["B7"].Owner.Should().Be(WhichPerspective.Player2); + board["B7"].IsPromoted.Should().Be(false); + board["C7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["C7"].Owner.Should().Be(WhichPerspective.Player2); + board["C7"].IsPromoted.Should().Be(false); + board["D7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["D7"].Owner.Should().Be(WhichPerspective.Player2); + board["D7"].IsPromoted.Should().Be(false); + board["E7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["E7"].Owner.Should().Be(WhichPerspective.Player2); + board["E7"].IsPromoted.Should().Be(false); + board["F7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["F7"].Owner.Should().Be(WhichPerspective.Player2); + board["F7"].IsPromoted.Should().Be(false); + board["G7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["G7"].Owner.Should().Be(WhichPerspective.Player2); + board["G7"].IsPromoted.Should().Be(false); + board["H7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["H7"].Owner.Should().Be(WhichPerspective.Player2); + board["H7"].IsPromoted.Should().Be(false); + board["I7"].WhichPiece.Should().Be(WhichPiece.Pawn); + board["I7"].Owner.Should().Be(WhichPerspective.Player2); + board["I7"].IsPromoted.Should().Be(false); - board["A8"].Should().BeNull(); - board["B8"].WhichPiece.Should().Be(WhichPiece.Rook); - board["B8"].Owner.Should().Be(WhichPlayer.Player2); - board["B8"].IsPromoted.Should().Be(false); - board["C8"].Should().BeNull(); - board["D8"].Should().BeNull(); - board["E8"].Should().BeNull(); - board["F8"].Should().BeNull(); - board["G8"].Should().BeNull(); - board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop); - board["H8"].Owner.Should().Be(WhichPlayer.Player2); - board["H8"].IsPromoted.Should().Be(false); - board["I8"].Should().BeNull(); + board["A8"].Should().BeNull(); + board["B8"].WhichPiece.Should().Be(WhichPiece.Rook); + board["B8"].Owner.Should().Be(WhichPerspective.Player2); + board["B8"].IsPromoted.Should().Be(false); + board["C8"].Should().BeNull(); + board["D8"].Should().BeNull(); + board["E8"].Should().BeNull(); + board["F8"].Should().BeNull(); + board["G8"].Should().BeNull(); + board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop); + board["H8"].Owner.Should().Be(WhichPerspective.Player2); + board["H8"].IsPromoted.Should().Be(false); + board["I8"].Should().BeNull(); - board["A9"].WhichPiece.Should().Be(WhichPiece.Lance); - board["A9"].Owner.Should().Be(WhichPlayer.Player2); - board["A9"].IsPromoted.Should().Be(false); - board["B9"].WhichPiece.Should().Be(WhichPiece.Knight); - board["B9"].Owner.Should().Be(WhichPlayer.Player2); - board["B9"].IsPromoted.Should().Be(false); - board["C9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); - board["C9"].Owner.Should().Be(WhichPlayer.Player2); - board["C9"].IsPromoted.Should().Be(false); - board["D9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); - board["D9"].Owner.Should().Be(WhichPlayer.Player2); - board["D9"].IsPromoted.Should().Be(false); - board["E9"].WhichPiece.Should().Be(WhichPiece.King); - board["E9"].Owner.Should().Be(WhichPlayer.Player2); - board["E9"].IsPromoted.Should().Be(false); - board["F9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); - board["F9"].Owner.Should().Be(WhichPlayer.Player2); - board["F9"].IsPromoted.Should().Be(false); - board["G9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); - board["G9"].Owner.Should().Be(WhichPlayer.Player2); - board["G9"].IsPromoted.Should().Be(false); - board["H9"].WhichPiece.Should().Be(WhichPiece.Knight); - board["H9"].Owner.Should().Be(WhichPlayer.Player2); - board["H9"].IsPromoted.Should().Be(false); - board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); - board["I9"].Owner.Should().Be(WhichPlayer.Player2); - board["I9"].IsPromoted.Should().Be(false); - } + board["A9"].WhichPiece.Should().Be(WhichPiece.Lance); + board["A9"].Owner.Should().Be(WhichPerspective.Player2); + board["A9"].IsPromoted.Should().Be(false); + board["B9"].WhichPiece.Should().Be(WhichPiece.Knight); + board["B9"].Owner.Should().Be(WhichPerspective.Player2); + board["B9"].IsPromoted.Should().Be(false); + board["C9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); + board["C9"].Owner.Should().Be(WhichPerspective.Player2); + board["C9"].IsPromoted.Should().Be(false); + board["D9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); + board["D9"].Owner.Should().Be(WhichPerspective.Player2); + board["D9"].IsPromoted.Should().Be(false); + board["E9"].WhichPiece.Should().Be(WhichPiece.King); + board["E9"].Owner.Should().Be(WhichPerspective.Player2); + board["E9"].IsPromoted.Should().Be(false); + board["F9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral); + board["F9"].Owner.Should().Be(WhichPerspective.Player2); + board["F9"].IsPromoted.Should().Be(false); + board["G9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral); + board["G9"].Owner.Should().Be(WhichPerspective.Player2); + board["G9"].IsPromoted.Should().Be(false); + board["H9"].WhichPiece.Should().Be(WhichPiece.Knight); + board["H9"].Owner.Should().Be(WhichPerspective.Player2); + board["H9"].IsPromoted.Should().Be(false); + board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); + board["I9"].Owner.Should().Be(WhichPerspective.Player2); + board["I9"].IsPromoted.Should().Be(false); + } - [Fact] - public void InitializeBoardStateWithMoves() - { - var moves = new[] - { + [Fact] + public void InitializeBoardStateWithMoves() + { + var moves = new[] + { // P1 Pawn new Move("A3", "A4") - }; - var shogi = new Shogi(moves); - shogi.Board["A3"].Should().BeNull(); - shogi.Board["A4"].WhichPiece.Should().Be(WhichPiece.Pawn); - } + }; + var shogi = new Shogi(moves); + shogi.Board["A3"].Should().BeNull(); + shogi.Board["A4"].WhichPiece.Should().Be(WhichPiece.Pawn); + } - [Fact] - public void PreventInvalidMoves_MoveFromEmptyPosition() - { - // Arrange - var shogi = new Shogi(); - shogi.Board["D5"].Should().BeNull(); + [Fact] + public void AllowValidMoves_AfterCheck() + { + // Arrange + var moves = new[] + { + // P1 Pawn + new Move("C3", "C4"), + // P2 Pawn + new Move("G7", "G6"), + // P1 Bishop puts P2 in check + new Move("B2", "G7"), + }; + var shogi = new Shogi(moves); + shogi.InCheck.Should().Be(WhichPerspective.Player2); - // Act - var moveSuccess = shogi.Move(new Move("D5", "D6")); + // Act - P2 is able to un-check theirself. + /// P2 King moves out of check + var moveSuccess = shogi.Move(new Move("E9", "E8")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.Board["D5"].Should().BeNull(); - shogi.Board["D6"].Should().BeNull(); - } + // Assert + using var _ = new AssertionScope(); + moveSuccess.Should().BeTrue(); + shogi.InCheck.Should().BeNull(); + } - [Fact] - public void PreventInvalidMoves_MoveToCurrentPosition() - { - // Arrange - var shogi = new Shogi(); + [Fact] + public void PreventInvalidMoves_MoveFromEmptyPosition() + { + // Arrange + var shogi = new Shogi(); + shogi.Board["D5"].Should().BeNull(); - // Act - P1 "moves" pawn to the position it already exists at. - var moveSuccess = shogi.Move(new Move("A3", "A3")); + // Act + var moveSuccess = shogi.Move(new Move("D5", "D6")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); - shogi.Player1Hand.Should().BeEmpty(); - shogi.Player2Hand.Should().BeEmpty(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.Board["D5"].Should().BeNull(); + shogi.Board["D6"].Should().BeNull(); + } - [Fact] - public void PreventInvalidMoves_MoveSet() - { - // Arrange - var shogi = new Shogi(); + [Fact] + public void PreventInvalidMoves_MoveToCurrentPosition() + { + // Arrange + var shogi = new Shogi(); - // Act - Move Lance illegally - var moveSuccess = shogi.Move(new Move("A1", "D5")); + // Act - P1 "moves" pawn to the position it already exists at. + var moveSuccess = shogi.Move(new Move("A3", "A3")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); - shogi.Board["A5"].Should().BeNull(); - shogi.Player1Hand.Should().BeEmpty(); - shogi.Player2Hand.Should().BeEmpty(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Player1Hand.Should().BeEmpty(); + shogi.Player2Hand.Should().BeEmpty(); + } - [Fact] - public void PreventInvalidMoves_Ownership() - { - // Arrange - var shogi = new Shogi(); - shogi.WhoseTurn.Should().Be(WhichPlayer.Player1); - shogi.Board["A7"].Owner.Should().Be(WhichPlayer.Player2); + [Fact] + public void PreventInvalidMoves_MoveSet() + { + // Arrange + var shogi = new Shogi(); - // Act - Move Player2 Pawn when it is Player1 turn. - var moveSuccess = shogi.Move(new Move("A7", "A6")); + // Act - Move Lance illegally + var moveSuccess = shogi.Move(new Move("A1", "D5")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.Board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn); - shogi.Board["A6"].Should().BeNull(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); + shogi.Board["A5"].Should().BeNull(); + shogi.Player1Hand.Should().BeEmpty(); + shogi.Player2Hand.Should().BeEmpty(); + } - [Fact] - public void PreventInvalidMoves_MoveThroughAllies() - { - // Arrange - var shogi = new Shogi(); + [Fact] + public void PreventInvalidMoves_Ownership() + { + // Arrange + var shogi = new Shogi(); + shogi.WhoseTurn.Should().Be(WhichPerspective.Player1); + shogi.Board["A7"].Owner.Should().Be(WhichPerspective.Player2); - // Act - Move P1 Lance through P1 Pawn. - var moveSuccess = shogi.Move(new Move("A1", "A5")); + // Act - Move Player2 Pawn when it is Player1 turn. + var moveSuccess = shogi.Move(new Move("A7", "A6")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); - shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); - shogi.Board["A5"].Should().BeNull(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.Board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Board["A6"].Should().BeNull(); + } - [Fact] - public void PreventInvalidMoves_CaptureAlly() - { - // Arrange - var shogi = new Shogi(); + [Fact] + public void PreventInvalidMoves_MoveThroughAllies() + { + // Arrange + var shogi = new Shogi(); - // Act - P1 Knight tries to capture P1 Pawn. - var moveSuccess = shogi.Move(new Move("B1", "C3")); + // Act - Move P1 Lance through P1 Pawn. + var moveSuccess = shogi.Move(new Move("A1", "A5")); - // Arrange - moveSuccess.Should().BeFalse(); - shogi.Board["B1"].WhichPiece.Should().Be(WhichPiece.Knight); - shogi.Board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn); - shogi.Player1Hand.Should().BeEmpty(); - shogi.Player2Hand.Should().BeEmpty(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance); + shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Board["A5"].Should().BeNull(); + } - [Fact] - public void PreventInvalidMoves_Check() - { - // Arrange - var moves = new[] - { + [Fact] + public void PreventInvalidMoves_CaptureAlly() + { + // Arrange + var shogi = new Shogi(); + + // Act - P1 Knight tries to capture P1 Pawn. + var moveSuccess = shogi.Move(new Move("B1", "C3")); + + // Arrange + moveSuccess.Should().BeFalse(); + shogi.Board["B1"].WhichPiece.Should().Be(WhichPiece.Knight); + shogi.Board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn); + shogi.Player1Hand.Should().BeEmpty(); + shogi.Player2Hand.Should().BeEmpty(); + } + + [Fact] + public void PreventInvalidMoves_Check() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4"), // P2 Pawn new Move("G7", "G6"), // P1 Bishop puts P2 in check new Move("B2", "G7") - }; - var shogi = new Shogi(moves); - shogi.InCheck.Should().Be(WhichPlayer.Player2); + }; + var shogi = new Shogi(moves); + shogi.InCheck.Should().Be(WhichPerspective.Player2); - // Act - P2 moves Lance while in check. - var moveSuccess = shogi.Move(new Move("I9", "I8")); + // Act - P2 moves Lance while in check. + var moveSuccess = shogi.Move(new Move("I9", "I8")); - // Assert - moveSuccess.Should().BeFalse(); - shogi.InCheck.Should().Be(WhichPlayer.Player2); - shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); - shogi.Board["I8"].Should().BeNull(); - } + // Assert + moveSuccess.Should().BeFalse(); + shogi.InCheck.Should().Be(WhichPerspective.Player2); + shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); + shogi.Board["I8"].Should().BeNull(); + } - [Fact] - public void PreventInvalidDrops_MoveSet() - { - // Arrange - var moves = new[] - { + [Fact] + public void PreventInvalidDrops_MoveSet() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4"), // P2 Pawn @@ -360,53 +386,53 @@ namespace Gameboard.ShogiUI.xUnitTests new Move("H9", "I9"), // P2 Pawn captures P1 Pawn new Move("I4", "I3") - }; - var shogi = new Shogi(moves); - shogi.Player1Hand.Count.Should().Be(4); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.WhoseTurn.Should().Be(WhichPlayer.Player1); + }; + var shogi = new Shogi(moves); + shogi.Player1Hand.Count.Should().Be(4); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.WhoseTurn.Should().Be(WhichPerspective.Player1); - // Act | Assert - Illegally placing Knight from the hand in farthest row. - shogi.Board["H9"].Should().BeNull(); - var dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9")); - dropSuccess.Should().BeFalse(); - shogi.Board["H9"].Should().BeNull(); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + // Act | Assert - Illegally placing Knight from the hand in farthest row. + shogi.Board["H9"].Should().BeNull(); + var dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9")); + dropSuccess.Should().BeFalse(); + shogi.Board["H9"].Should().BeNull(); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - // Act | Assert - Illegally placing Knight from the hand in second farthest row. - shogi.Board["H8"].Should().BeNull(); - dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H8")); - dropSuccess.Should().BeFalse(); - shogi.Board["H8"].Should().BeNull(); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + // Act | Assert - Illegally placing Knight from the hand in second farthest row. + shogi.Board["H8"].Should().BeNull(); + dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H8")); + dropSuccess.Should().BeFalse(); + shogi.Board["H8"].Should().BeNull(); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - // Act | Assert - Illegally place Lance from the hand. - shogi.Board["H9"].Should().BeNull(); - dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9")); - dropSuccess.Should().BeFalse(); - shogi.Board["H9"].Should().BeNull(); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + // Act | Assert - Illegally place Lance from the hand. + shogi.Board["H9"].Should().BeNull(); + dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9")); + dropSuccess.Should().BeFalse(); + shogi.Board["H9"].Should().BeNull(); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - // Act | Assert - Illegally place Pawn from the hand. - shogi.Board["H9"].Should().BeNull(); - dropSuccess = shogi.Move(new Move(WhichPiece.Pawn, "H9")); - dropSuccess.Should().BeFalse(); - shogi.Board["H9"].Should().BeNull(); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + // Act | Assert - Illegally place Pawn from the hand. + shogi.Board["H9"].Should().BeNull(); + dropSuccess = shogi.Move(new Move(WhichPiece.Pawn, "H9")); + dropSuccess.Should().BeFalse(); + shogi.Board["H9"].Should().BeNull(); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn. - // TODO - } + // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn. + // TODO + } - [Fact] - public void PreventInvalidDrop_Check() - { - // Arrange - var moves = new[] - { + [Fact] + public void PreventInvalidDrop_Check() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4"), // P2 Pawn @@ -421,28 +447,28 @@ namespace Gameboard.ShogiUI.xUnitTests new Move("A7", "A6"), // P1 drop Bishop, place P2 in check new Move(WhichPiece.Bishop, "G7") - }; - var shogi = new Shogi(moves); - shogi.InCheck.Should().Be(WhichPlayer.Player2); - shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.Board["E5"].Should().BeNull(); + }; + var shogi = new Shogi(moves); + shogi.InCheck.Should().Be(WhichPerspective.Player2); + shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.Board["E5"].Should().BeNull(); - // Act - P2 places a Bishop while in check. - var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "E5")); + // Act - P2 places a Bishop while in check. + var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "E5")); - // Assert - dropSuccess.Should().BeFalse(); - shogi.Board["E5"].Should().BeNull(); - shogi.InCheck.Should().Be(WhichPlayer.Player2); - shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - } + // Assert + dropSuccess.Should().BeFalse(); + shogi.Board["E5"].Should().BeNull(); + shogi.InCheck.Should().Be(WhichPerspective.Player2); + shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + } - [Fact] - public void PreventInvalidDrop_Capture() - { - // Arrange - var moves = new[] - { + [Fact] + public void PreventInvalidDrop_Capture() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4"), // P2 Pawn @@ -451,84 +477,84 @@ namespace Gameboard.ShogiUI.xUnitTests new Move("B2", "H8"), // P2 Pawn new Move("G6", "G5") - }; - var shogi = new Shogi(moves); - using (new AssertionScope()) - { - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.Board["I9"].Should().NotBeNull(); - shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); - shogi.Board["I9"].Owner.Should().Be(WhichPlayer.Player2); - } + }; + var shogi = new Shogi(moves); + using (new AssertionScope()) + { + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.Board["I9"].Should().NotBeNull(); + shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); + shogi.Board["I9"].Owner.Should().Be(WhichPerspective.Player2); + } - // Act - P1 tries to place a piece where an opponent's piece resides. - var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "I9")); + // Act - P1 tries to place a piece where an opponent's piece resides. + var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "I9")); - // Assert - using (new AssertionScope()) - { - dropSuccess.Should().BeFalse(); - shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.Board["I9"].Should().NotBeNull(); - shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); - shogi.Board["I9"].Owner.Should().Be(WhichPlayer.Player2); - } - } + // Assert + using (new AssertionScope()) + { + dropSuccess.Should().BeFalse(); + shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.Board["I9"].Should().NotBeNull(); + shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance); + shogi.Board["I9"].Owner.Should().Be(WhichPerspective.Player2); + } + } - [Fact] - public void Check() - { - // Arrange - var moves = new[] - { + [Fact] + public void Check() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4"), // P2 Pawn new Move("G7", "G6"), - }; - var shogi = new Shogi(moves); + }; + var shogi = new Shogi(moves); - // Act - P1 Bishop, check - shogi.Move(new Move("B2", "G7")); + // Act - P1 Bishop, check + shogi.Move(new Move("B2", "G7")); - // Assert - shogi.InCheck.Should().Be(WhichPlayer.Player2); - } + // Assert + shogi.InCheck.Should().Be(WhichPerspective.Player2); + } - [Fact] - public void Promote() - { - // Arrange - var moves = new[] - { + [Fact] + public void Promote() + { + // Arrange + var moves = new[] + { // P1 Pawn new Move("C3", "C4" ), // P2 Pawn new Move("G7", "G6" ) - }; - var shogi = new Shogi(moves); + }; + var shogi = new Shogi(moves); - // Act - P1 moves across promote threshold. - var moveSuccess = shogi.Move(new Move("B2", "G7", true)); + // Act - P1 moves across promote threshold. + var moveSuccess = shogi.Move(new Move("B2", "G7", true)); - // Assert - using (new AssertionScope()) - { - moveSuccess.Should().BeTrue(); - shogi.Board["B2"].Should().BeNull(); - shogi.Board["G7"].Should().NotBeNull(); - shogi.Board["G7"].WhichPiece.Should().Be(WhichPiece.Bishop); - shogi.Board["G7"].Owner.Should().Be(WhichPlayer.Player1); - shogi.Board["G7"].IsPromoted.Should().BeTrue(); - } - } + // Assert + using (new AssertionScope()) + { + moveSuccess.Should().BeTrue(); + shogi.Board["B2"].Should().BeNull(); + shogi.Board["G7"].Should().NotBeNull(); + shogi.Board["G7"].WhichPiece.Should().Be(WhichPiece.Bishop); + shogi.Board["G7"].Owner.Should().Be(WhichPerspective.Player1); + shogi.Board["G7"].IsPromoted.Should().BeTrue(); + } + } - [Fact] - public void CheckMate() - { - // Arrange - var moves = new[] - { + [Fact] + public void CheckMate() + { + // Arrange + var moves = new[] + { // P1 Rook new Move("H2", "E2"), // P2 Gold @@ -549,46 +575,46 @@ namespace Gameboard.ShogiUI.xUnitTests new Move("E6", "E7", true), // P2 King retreat new Move("E8", "E9"), - }; - var shogi = new Shogi(moves); - output.WriteLine(shogi.PrintStateAsAscii()); + }; + var shogi = new Shogi(moves); + output.WriteLine(shogi.PrintStateAsAscii()); - // Act - P1 Pawn wins by checkmate. - var moveSuccess = shogi.Move(new Move("E7", "E8")); - output.WriteLine(shogi.PrintStateAsAscii()); + // Act - P1 Pawn wins by checkmate. + var moveSuccess = shogi.Move(new Move("E7", "E8")); + output.WriteLine(shogi.PrintStateAsAscii()); - // Assert - checkmate - moveSuccess.Should().BeTrue(); - shogi.IsCheckmate.Should().BeTrue(); - shogi.InCheck.Should().Be(WhichPlayer.Player2); - } + // Assert - checkmate + moveSuccess.Should().BeTrue(); + shogi.IsCheckmate.Should().BeTrue(); + shogi.InCheck.Should().Be(WhichPerspective.Player2); + } - [Fact] - public void Capture() - { - // Arrange - var moves = new[] - { - new Move("C3", "C4"), - new Move("G7", "G6") - }; - var shogi = new Shogi(moves); + [Fact] + public void Capture() + { + // Arrange + var moves = new[] + { + new Move("C3", "C4"), + new Move("G7", "G6") + }; + var shogi = new Shogi(moves); - // Act - P1 Bishop captures P2 Bishop - var moveSuccess = shogi.Move(new Move("B2", "H8")); + // Act - P1 Bishop captures P2 Bishop + var moveSuccess = shogi.Move(new Move("B2", "H8")); - // Assert - moveSuccess.Should().BeTrue(); - shogi.Board["B2"].Should().BeNull(); - shogi.Board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop); - shogi.Board["H8"].Owner.Should().Be(WhichPlayer.Player1); - shogi.Board.Values - .Where(p => p != null) - .Should().ContainSingle(piece => piece.WhichPiece == WhichPiece.Bishop); + // Assert + moveSuccess.Should().BeTrue(); + shogi.Board["B2"].Should().BeNull(); + shogi.Board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop); + shogi.Board["H8"].Owner.Should().Be(WhichPerspective.Player1); + shogi.Board.Values + .Where(p => p != null) + .Should().ContainSingle(piece => piece.WhichPiece == WhichPiece.Bishop); - shogi.Player1Hand - .Should() - .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPlayer.Player1); - } - } + shogi.Player1Hand + .Should() + .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPerspective.Player1); + } + } }