From 79b70d6fa528e8901a8a4185ebb0586f7c0e49ed Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Fri, 11 Nov 2022 18:42:27 -0600 Subject: [PATCH] yep --- Shogi.Api/Controllers/SessionsController.cs | 87 +------ Shogi.Api/Models/User.cs | 5 +- Shogi.Api/Repositories/SessionRepository.cs | 11 +- .../Post Deployment/Script.PostDeployment.sql | 3 +- .../Session/Stored Procedures/CreateMove.sql | 27 +- Shogi.UI/Pages/Home/Api/IShogiApi.cs | 2 +- Shogi.UI/Pages/Home/Api/ShogiApi.cs | 4 +- Shogi.UI/Pages/Home/GameBoard.razor | 185 ++++++++++---- Shogi.UI/Pages/Home/GameBoard.razor.css | 36 ++- Shogi.UI/Pages/Home/GameBrowser.razor | 187 +++++++------- Shogi.UI/wwwroot/css/app.css | 3 +- Tests/AcceptanceTests/AcceptanceTests.cs | 230 ++++++++++++++++- Tests/UnitTests/ShogiBoardStateShould.cs | 240 +++++++++--------- 13 files changed, 656 insertions(+), 364 deletions(-) diff --git a/Shogi.Api/Controllers/SessionsController.cs b/Shogi.Api/Controllers/SessionsController.cs index 6d8197b..1b545c7 100644 --- a/Shogi.Api/Controllers/SessionsController.cs +++ b/Shogi.Api/Controllers/SessionsController.cs @@ -68,7 +68,7 @@ public class SessionsController : ControllerBase return this.NoContent(); } - return this.Forbid("Cannot delete sessions created by others."); + return this.StatusCode(StatusCodes.Status403Forbidden, "Cannot delete sessions created by others."); } [HttpGet("PlayerCount")] @@ -108,15 +108,15 @@ public class SessionsController : ControllerBase }; } - [HttpPatch("{name}/Move")] - public async Task Move([FromRoute] string name, [FromBody] MovePieceCommand command) + [HttpPatch("{sessionName}/Move")] + public async Task Move([FromRoute] string sessionName, [FromBody] MovePieceCommand command) { var userId = User.GetShogiUserId(); - var session = await sessionRepository.ReadSession(name); + var session = await sessionRepository.ReadSession(sessionName); if (session == null) return this.NotFound("Shogi session does not exist."); - if (!session.IsSeated(userId)) return this.Forbid("Player is not a member of the Shogi session."); + if (!session.IsSeated(userId)) return this.StatusCode(StatusCodes.Status403Forbidden, "Player is not a member of the Shogi session."); try { @@ -126,14 +126,14 @@ public class SessionsController : ControllerBase } else { - session.Board.Move(command.From!, command.To, command.IsPromotion!.Value); + session.Board.Move(command.From!, command.To, command.IsPromotion ?? false); } } - catch (InvalidOperationException) + catch (InvalidOperationException e) { - return this.Conflict("Move is illegal."); + return this.Conflict(e.Message); } - // TODO: sessionRespository.SaveMove(); + await sessionRepository.CreateMove(sessionName, command); await communicationManager.BroadcastToPlayers( new PlayerHasMovedMessage { @@ -145,73 +145,4 @@ public class SessionsController : ControllerBase return this.NoContent(); } - - //[HttpPost("{sessionName}/Move")] - //public async Task MovePiece([FromRoute] string sessionName, [FromBody] MovePieceCommand request) - //{ - - // var user = await gameboardManager.ReadUser(User); - // var session = await gameboardRepository.ReadSession(sessionName); - // if (session == null) - // { - // return NotFound(); - // } - // if (user == null || (session.Player1 != user.Id && session.Player2 != user.Id)) - // { - // return Forbid("User is not seated at this game."); - // } - - // try - // { - // var move = request.Move; - // if (move.PieceFromCaptured.HasValue) - // session.Move(mapper.Map(move.PieceFromCaptured.Value), move.To); - // else if (!string.IsNullOrWhiteSpace(move.From)) - // session.Move(move.From, move.To, move.IsPromotion); - - // await gameboardRepository.CreateBoardState(session); - // await communicationManager.BroadcastToPlayers( - // new MoveResponse - // { - // SessionName = session.Name, - // PlayerName = user.Id - // }, - // session.Player1, - // session.Player2); - - // return Ok(); - // } - // catch (InvalidOperationException ex) - // { - // return Conflict(ex.Message); - // } - //} - - //[HttpPut("{sessionName}")] - //public async Task PutJoinSession([FromRoute] string sessionName) - //{ - // var user = await ReadUserOrThrow(); - // var session = await gameboardRepository.ReadSessionMetaData(sessionName); - // 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.Id); - // await gameboardRepository.UpdateSession(session); - - // var opponentName = user.Id == session.Player1 - // ? session.Player2! - // : session.Player1; - // await communicationManager.BroadcastToPlayers(new JoinSessionResponse - // { - // SessionName = session.Name, - // PlayerName = user.Id - // }, opponentName); - // return Ok(); - //} } diff --git a/Shogi.Api/Models/User.cs b/Shogi.Api/Models/User.cs index 568074e..3016b0e 100644 --- a/Shogi.Api/Models/User.cs +++ b/Shogi.Api/Models/User.cs @@ -5,10 +5,11 @@ namespace Shogi.Api.Models; public class User { public static readonly ReadOnlyCollection Adjectives = new(new[] { - "Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly" + "Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly", "Queenly", "Blushing", "Brave", + "Brainy", "Eager", "Itchy", "Fierce" }); public static readonly ReadOnlyCollection Subjects = new(new[] { - "Hippo", "Basil", "Mouse", "Walnut", "Prince", "Lima Bean", "Coala", "Potato", "Penguin" + "Hippo", "Basil", "Mouse", "Walnut", "Minstrel", "Lima Bean", "Koala", "Potato", "Penguin", "Cola", "Banana", "Egg", "Fish", "Yak" }); public static User CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft); public static User CreateGuestUser(string id) diff --git a/Shogi.Api/Repositories/SessionRepository.cs b/Shogi.Api/Repositories/SessionRepository.cs index b4ce151..8cadfa1 100644 --- a/Shogi.Api/Repositories/SessionRepository.cs +++ b/Shogi.Api/Repositories/SessionRepository.cs @@ -1,6 +1,6 @@ using Dapper; -using Shogi.Api.Extensions; using Shogi.Api.Repositories.Dto; +using Shogi.Contracts.Api; using Shogi.Domain; using System.Data; using System.Data.SqlClient; @@ -74,9 +74,11 @@ public class SessionRepository : ISessionRepository "session.CreateMove", new { - To = command.To, - From = command.From, - IsPromotion = command.IsPromotion + command.To, + command.From, + command.IsPromotion, + command.PieceFromHand, + SessionName = sessionName }, commandType: CommandType.StoredProcedure); } @@ -84,6 +86,7 @@ public class SessionRepository : ISessionRepository public interface ISessionRepository { + Task CreateMove(string sessionName, MovePieceCommand command); Task CreateSession(Session session); Task DeleteSession(string name); Task ReadSession(string name); diff --git a/Shogi.Database/Post Deployment/Script.PostDeployment.sql b/Shogi.Database/Post Deployment/Script.PostDeployment.sql index d14db3c..dd53baf 100644 --- a/Shogi.Database/Post Deployment/Script.PostDeployment.sql +++ b/Shogi.Database/Post Deployment/Script.PostDeployment.sql @@ -11,4 +11,5 @@ Post-Deployment Script Template */ :r .\Scripts\PopulateLoginPlatforms.sql -:r .\Scripts\PopulatePieces.sql \ No newline at end of file +:r .\Scripts\PopulatePieces.sql +:r .\Scripts\EnableSnapshotIsolationLevel.sql \ No newline at end of file diff --git a/Shogi.Database/Session/Stored Procedures/CreateMove.sql b/Shogi.Database/Session/Stored Procedures/CreateMove.sql index 37f4d82..ac4b184 100644 --- a/Shogi.Database/Session/Stored Procedures/CreateMove.sql +++ b/Shogi.Database/Session/Stored Procedures/CreateMove.sql @@ -2,8 +2,31 @@ @To VARCHAR(2), @From VARCHAR(2), @IsPromotion BIT, - @PieceName NVARCHAR(13), + @PieceFromHand NVARCHAR(13), @SessionName [session].[SessionName] AS -INSERT INTO [session].[Move] +BEGIN + SET NOCOUNT ON -- Performance boost + SET XACT_ABORT ON -- Rollback transaction on error + SET TRANSACTION ISOLATION LEVEL SNAPSHOT -- Ignores data changes that happen after the transaction begins. + + BEGIN TRANSACTION + + DECLARE @SessionId BIGINT = 0; + SELECT @SessionId = Id + FROM [session].[Session] + WHERE [Name] = @SessionName; + + DECLARE @PieceIdFromhand INT = NULL; + SELECT @PieceIdFromhand + FROM [session].[Piece] + WHERE [Name] = @PieceFromHand; + + INSERT INTO [session].[Move] + (SessionId, [To], [From], IsPromotion, PieceIdFromHand) + VALUES + (@SessionId, @To, @From, @IsPromotion, @PieceIdFromhand); + + COMMIT +END \ No newline at end of file diff --git a/Shogi.UI/Pages/Home/Api/IShogiApi.cs b/Shogi.UI/Pages/Home/Api/IShogiApi.cs index 16b89f3..a77ee7f 100644 --- a/Shogi.UI/Pages/Home/Api/IShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/IShogiApi.cs @@ -10,6 +10,6 @@ public interface IShogiApi Task GetSessionsPlayerCount(); Task GetToken(); Task GuestLogout(); - Task PostMove(string sessionName, Move move); + Task PostMove(string sessionName, MovePieceCommand move); Task PostSession(string name, bool isPrivate); } \ No newline at end of file diff --git a/Shogi.UI/Pages/Home/Api/ShogiApi.cs b/Shogi.UI/Pages/Home/Api/ShogiApi.cs index 19f1e98..ab1accc 100644 --- a/Shogi.UI/Pages/Home/Api/ShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/ShogiApi.cs @@ -63,9 +63,9 @@ namespace Shogi.UI.Pages.Home.Api return response; } - public async Task PostMove(string sessionName, Contracts.Types.Move move) + public async Task PostMove(string sessionName, MovePieceCommand command) { - await this.HttpClient.PostAsJsonAsync($"Sessions{sessionName}/Move", new MovePieceCommand { Move = move }); + await this.HttpClient.PostAsJsonAsync($"Sessions{sessionName}/Move", command); } public async Task PostSession(string name, bool isPrivate) diff --git a/Shogi.UI/Pages/Home/GameBoard.razor b/Shogi.UI/Pages/Home/GameBoard.razor index 0819111..82208c1 100644 --- a/Shogi.UI/Pages/Home/GameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard.razor @@ -2,61 +2,142 @@ @inject IShogiApi ShogiApi @inject AccountState Account; -
- @for (var rank = 9; rank > 0; rank--) - { - foreach (var file in Files) - { - var position = $"{file}{rank}"; - var piece = session?.BoardState.Board[position]; -
- -
- } - } -
- 9 - 8 - 7 - 6 - 5 - 4 - 3 - 2 - 1 -
-
- A - B - C - D - E - F - G - H - I -
-
+
+ +
+ @for (var rank = 1; rank < 10; rank++) + { + foreach (var file in Files) + { + var position = $"{file}{rank}"; + var piece = session?.BoardState.Board[position]; +
+ +
+ } + } +
+ 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 +
+
+ A + B + C + D + E + F + G + H + I +
+
+ + @if (session != null) + { + + + } +
@code { - [Parameter] - public string? SessionName { get; set; } - static readonly string[] Files = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I" }; - WhichPlayer Perspective => Account.User?.Id == session?.Player1 ? WhichPlayer.Player1 : WhichPlayer.Player2; + [Parameter] + public string? SessionName { get; set; } - Session? session; - string? selectedPosition; + static readonly string[] Files = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I" }; + WhichPlayer Perspective => Account.User?.Id == session?.Player1 + ? WhichPlayer.Player1 + : WhichPlayer.Player2; + Session? session; + IReadOnlyCollection OpponentHand + { + get + { + if (this.session == null) return Array.Empty(); - protected override async Task OnParametersSetAsync() - { - if (!string.IsNullOrWhiteSpace(SessionName)) - { - this.session = await ShogiApi.GetSession(SessionName); - } - } + return Perspective == WhichPlayer.Player1 + ? this.session.BoardState.Player1Hand + : this.session.BoardState.Player2Hand; + } + } + IReadOnlyCollection UserHand + { + get + { + if (this.session == null) return Array.Empty(); - void OnClickTile(Piece? piece, string position) - { - - } + return Perspective == WhichPlayer.Player1 + ? this.session.BoardState.Player1Hand + : this.session.BoardState.Player2Hand; + } + } + + string? selectedPosition; + WhichPiece? selectedPiece; + + protected override async Task OnParametersSetAsync() + { + if (!string.IsNullOrWhiteSpace(SessionName)) + { + this.session = await ShogiApi.GetSession(SessionName); + } + } + + void OnClickTile(Piece? piece, string position) + { + if (selectedPosition == null) + { + selectedPosition = position; + return; + } + else if (selectedPosition == position) + { + selectedPosition = null; + return; + } + else if (piece != null) + { + ShogiApi.PostMove(SessionName!, new Contracts.Api.MovePieceCommand + { + From = selectedPosition, + To = position, + IsP + }); + } + } + void OnClickHand(Piece piece) + { + selectedPiece = piece.WhichPiece; + } } diff --git a/Shogi.UI/Pages/Home/GameBoard.razor.css b/Shogi.UI/Pages/Home/GameBoard.razor.css index 24f4769..b3e7197 100644 --- a/Shogi.UI/Pages/Home/GameBoard.razor.css +++ b/Shogi.UI/Pages/Home/GameBoard.razor.css @@ -1,5 +1,20 @@ .game-board { + display: grid; + grid-template-areas: "board side-board"; + grid-template-columns: 3fr minmax(10rem, 1fr); + gap: 0.5rem; background-color: #444; +} + +.board { + grid-area: board; +} + +.side-board { + grid-area: side-board; +} + +.board { display: grid; grid-template-areas: "rank A9 B9 C9 D9 E9 F9 G9 H9 I9" @@ -20,7 +35,7 @@ gap: 3px; } - .game-board[data-perspective="Player2"] { + .board[data-perspective="Player2"] { grid-template-areas: "file file file file file file file file file ." "I1 H1 G1 F1 E1 D1 C1 B1 A1 rank" @@ -41,6 +56,11 @@ display: grid; place-content: center; padding: 0.25rem; + overflow: hidden; /* Because SVGs are shaped weird */ + transition: filter linear 0.25s; +} +.tile[data-selected] { + filter: invert(0.8); } .ruler { @@ -54,10 +74,20 @@ flex-direction: column; } -.game-board[data-perspective="Player2"] .ruler { +.board[data-perspective="Player2"] .ruler { flex-direction: row-reverse; } - .game-board[data-perspective="Player2"] .ruler.vertical { + .board[data-perspective="Player2"] .ruler.vertical { flex-direction: column-reverse; } + +.side-board { + display: grid; + grid-template-rows: auto 1fr auto; +} + + .side-board .hand { + display: flex; + flex-wrap: wrap; + } diff --git a/Shogi.UI/Pages/Home/GameBrowser.razor b/Shogi.UI/Pages/Home/GameBrowser.razor index 5030401..a646d1a 100644 --- a/Shogi.UI/Pages/Home/GameBrowser.razor +++ b/Shogi.UI/Pages/Home/GameBrowser.razor @@ -3,107 +3,116 @@ @using System.Net @inject IShogiApi ShogiApi; @inject ShogiSocket ShogiSocket; +@inject AccountState Account;
- + -
-
-
- @if (!sessions.Any()) - { -

No games exist

- } - @foreach (var session in sessions) - { - - } -
-
-
- - +
+
+
+ @if (!sessions.Any()) + { +

No games exist

+ } + @foreach (var session in sessions) + { + + } +
+
+
+ + -

Start a new session

-
- - -
-
-
- - -
- -
+

Start a new session

+
+ + +
+
+
+ + +
+ +
- @if (createSessionStatusCode == HttpStatusCode.Created) - { - - } - else if (createSessionStatusCode == HttpStatusCode.Conflict) - { - - } + @if (createSessionStatusCode == HttpStatusCode.Created) + { + + } + else if (createSessionStatusCode == HttpStatusCode.Conflict) + { + + } -
-
-
+
+
+
@code { - [Parameter] - public Action? ActiveSessionChanged { get; set; } - CreateForm createForm = new(); - SessionMetadata[] sessions = Array.Empty(); - SessionMetadata? activeSession; - HttpStatusCode? createSessionStatusCode; + [Parameter] + public Action? ActiveSessionChanged { get; set; } + CreateForm createForm = new(); + SessionMetadata[] sessions = Array.Empty(); + SessionMetadata? activeSession; + HttpStatusCode? createSessionStatusCode; - protected override async Task OnInitializedAsync() - { - ShogiSocket.OnCreateGameMessage += async (sender, message) => await FetchSessions(); - await FetchSessions(); - } - string ActiveCss(SessionMetadata s) => s == activeSession ? "active" : string.Empty; + protected override async Task OnInitializedAsync() + { + ShogiSocket.OnCreateGameMessage += async (sender, message) => await FetchSessions(); + Account.LoginChangedEvent += async (sender, message) => + { + if (message.User != null) + { + await FetchSessions(); + } + }; + } - void OnClickSession(SessionMetadata s) - { - activeSession = s; - ActiveSessionChanged?.Invoke(s); - } + string ActiveCss(SessionMetadata s) => s == activeSession ? "active" : string.Empty; - async Task FetchSessions() - { - var sessions = await ShogiApi.GetSessionsPlayerCount(); - if (sessions != null) - { - this.sessions = sessions.PlayerHasJoinedSessions.Concat(sessions.AllOtherSessions).ToArray(); - } - } + void OnClickSession(SessionMetadata s) + { + activeSession = s; + ActiveSessionChanged?.Invoke(s); + } - async Task CreateSession() - { - createSessionStatusCode = await ShogiApi.PostSession(createForm.Name, createForm.IsPrivate); - } + async Task FetchSessions() + { + var sessions = await ShogiApi.GetSessionsPlayerCount(); + if (sessions != null) + { + this.sessions = sessions.PlayerHasJoinedSessions.Concat(sessions.AllOtherSessions).ToArray(); + StateHasChanged(); + } + } - private class CreateForm - { - [Required] - public string Name { get; set; } = string.Empty; - public bool IsPrivate { get; set; } - } + async Task CreateSession() + { + createSessionStatusCode = await ShogiApi.PostSession(createForm.Name, createForm.IsPrivate); + } + + private class CreateForm + { + [Required] + public string Name { get; set; } = string.Empty; + public bool IsPrivate { get; set; } + } } diff --git a/Shogi.UI/wwwroot/css/app.css b/Shogi.UI/wwwroot/css/app.css index 341c863..3ed9a43 100644 --- a/Shogi.UI/wwwroot/css/app.css +++ b/Shogi.UI/wwwroot/css/app.css @@ -7,9 +7,8 @@ html, body, #app { body { margin: 0; padding: 0; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: var(--primary-color); - font-family: cursive; } span { diff --git a/Tests/AcceptanceTests/AcceptanceTests.cs b/Tests/AcceptanceTests/AcceptanceTests.cs index d5f9b21..b62c53d 100644 --- a/Tests/AcceptanceTests/AcceptanceTests.cs +++ b/Tests/AcceptanceTests/AcceptanceTests.cs @@ -1,3 +1,4 @@ +using FluentAssertions.Execution; using Shogi.AcceptanceTests.TestSetup; using Shogi.Contracts.Api; using Shogi.Contracts.Types; @@ -28,7 +29,7 @@ public class AcceptanceTests : IClassFixture try { // Arrange - await CreateSession(); + await SetupTestSession(); // Act var readAllResponse = await Service @@ -45,17 +46,17 @@ public class AcceptanceTests : IClassFixture finally { // Annul - await DeleteSession(); + await DeleteTestSession(); } } [Fact] - public async Task CreateAndReadSession() + public async Task CreateSession() { try { // Arrange - await CreateSession(); + await SetupTestSession(); // Act var response = await Service.GetFromJsonAsync( @@ -66,6 +67,7 @@ public class AcceptanceTests : IClassFixture response.Should().NotBeNull(); response!.Session.Should().NotBeNull(); response.Session.BoardState.Board.Should().NotBeEmpty(); + ValidateBoard(response.Session.BoardState.Board); response.Session.BoardState.Player1Hand.Should().BeEmpty(); response.Session.BoardState.Player2Hand.Should().BeEmpty(); response.Session.BoardState.PlayerInCheck.Should().BeNull(); @@ -77,11 +79,218 @@ public class AcceptanceTests : IClassFixture finally { // Annul - await DeleteSession(); + await DeleteTestSession(); + } + + static void ValidateBoard(Dictionary board) + { + using var scope = new AssertionScope(); + 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); + + 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["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["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["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["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["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); + } } - private async Task CreateSession() + [Fact] + public async Task MovePieceCommand_MovingPieceFromBoard_MovesThePiece() + { + try + { + // Arrange + await SetupTestSession(); + var movePawnCommand = new MovePieceCommand + { + From = "A3", + To = "A4", + }; + + // Act + var response = await Service.PatchAsync(new Uri("Sessions/Acceptance Tests/Move", UriKind.Relative), JsonContent.Create(movePawnCommand)); + response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync()); + + // Assert + var session = (await ReadTestSession()).Session; + session.BoardState.Board["A2"].Should().BeNull(); + session.BoardState.Board["A3"].Should().NotBeNull(); + session.BoardState.Board["A3"]!.IsPromoted.Should().BeFalse(); + session.BoardState.Board["A3"]!.Owner.Should().Be(WhichPlayer.Player1); + session.BoardState.Board["A3"]!.WhichPiece.Should().Be(WhichPiece.Pawn); + } + finally + { + // Annul + await DeleteTestSession(); + } + } + + private async Task SetupTestSession() { var createResponse = await Service.PostAsJsonAsync( new Uri("Sessions", UriKind.Relative), @@ -90,10 +299,15 @@ public class AcceptanceTests : IClassFixture createResponse.StatusCode.Should().Be(HttpStatusCode.Created); } - private async Task DeleteSession() + private Task ReadTestSession() + { + return Service.GetFromJsonAsync(new Uri("Sessions/Acceptance Tests", UriKind.Relative))!; + } + + private async Task DeleteTestSession() { var response = await Service.DeleteAsync(new Uri("Sessions/Acceptance Tests", UriKind.Relative)); - response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: "Test cleanup should succeed"); + response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync()); } } \ No newline at end of file diff --git a/Tests/UnitTests/ShogiBoardStateShould.cs b/Tests/UnitTests/ShogiBoardStateShould.cs index 26588cd..9d0ad55 100644 --- a/Tests/UnitTests/ShogiBoardStateShould.cs +++ b/Tests/UnitTests/ShogiBoardStateShould.cs @@ -9,75 +9,75 @@ public class ShogiBoardStateShould var board = BoardState.StandardStarting; // 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); + 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); 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["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["H2"]!.WhichPiece.Should().Be(WhichPiece.Rook); + board["H2"]!.Owner.Should().Be(WhichPlayer.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(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["A4"].Should().BeNull(); board["B4"].Should().BeNull(); @@ -109,74 +109,74 @@ public class ShogiBoardStateShould 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(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["A8"].Should().BeNull(); - board["B8"]?.WhichPiece.Should().Be(WhichPiece.Rook); - board["B8"]?.Owner.Should().Be(WhichPlayer.Player2); - board["B8"]?.IsPromoted.Should().Be(false); + 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["H8"]!.WhichPiece.Should().Be(WhichPiece.Bishop); + board["H8"]!.Owner.Should().Be(WhichPlayer.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(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); } }