This commit is contained in:
2022-11-11 18:42:27 -06:00
parent b89760af8e
commit 79b70d6fa5
13 changed files with 656 additions and 364 deletions

View File

@@ -68,7 +68,7 @@ public class SessionsController : ControllerBase
return this.NoContent(); 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")] [HttpGet("PlayerCount")]
@@ -108,15 +108,15 @@ public class SessionsController : ControllerBase
}; };
} }
[HttpPatch("{name}/Move")] [HttpPatch("{sessionName}/Move")]
public async Task<IActionResult> Move([FromRoute] string name, [FromBody] MovePieceCommand command) public async Task<IActionResult> Move([FromRoute] string sessionName, [FromBody] MovePieceCommand command)
{ {
var userId = User.GetShogiUserId(); 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 == 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 try
{ {
@@ -126,14 +126,14 @@ public class SessionsController : ControllerBase
} }
else 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( await communicationManager.BroadcastToPlayers(
new PlayerHasMovedMessage new PlayerHasMovedMessage
{ {
@@ -145,73 +145,4 @@ public class SessionsController : ControllerBase
return this.NoContent(); return this.NoContent();
} }
//[HttpPost("{sessionName}/Move")]
//public async Task<IActionResult> 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<IActionResult> 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();
//}
} }

View File

@@ -5,10 +5,11 @@ namespace Shogi.Api.Models;
public class User public class User
{ {
public static readonly ReadOnlyCollection<string> Adjectives = new(new[] { public static readonly ReadOnlyCollection<string> 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<string> Subjects = new(new[] { public static readonly ReadOnlyCollection<string> 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 CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft);
public static User CreateGuestUser(string id) public static User CreateGuestUser(string id)

View File

@@ -1,6 +1,6 @@
using Dapper; using Dapper;
using Shogi.Api.Extensions;
using Shogi.Api.Repositories.Dto; using Shogi.Api.Repositories.Dto;
using Shogi.Contracts.Api;
using Shogi.Domain; using Shogi.Domain;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
@@ -74,9 +74,11 @@ public class SessionRepository : ISessionRepository
"session.CreateMove", "session.CreateMove",
new new
{ {
To = command.To, command.To,
From = command.From, command.From,
IsPromotion = command.IsPromotion command.IsPromotion,
command.PieceFromHand,
SessionName = sessionName
}, },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
@@ -84,6 +86,7 @@ public class SessionRepository : ISessionRepository
public interface ISessionRepository public interface ISessionRepository
{ {
Task CreateMove(string sessionName, MovePieceCommand command);
Task CreateSession(Session session); Task CreateSession(Session session);
Task DeleteSession(string name); Task DeleteSession(string name);
Task<Session?> ReadSession(string name); Task<Session?> ReadSession(string name);

View File

@@ -12,3 +12,4 @@ Post-Deployment Script Template
:r .\Scripts\PopulateLoginPlatforms.sql :r .\Scripts\PopulateLoginPlatforms.sql
:r .\Scripts\PopulatePieces.sql :r .\Scripts\PopulatePieces.sql
:r .\Scripts\EnableSnapshotIsolationLevel.sql

View File

@@ -2,8 +2,31 @@
@To VARCHAR(2), @To VARCHAR(2),
@From VARCHAR(2), @From VARCHAR(2),
@IsPromotion BIT, @IsPromotion BIT,
@PieceName NVARCHAR(13), @PieceFromHand NVARCHAR(13),
@SessionName [session].[SessionName] @SessionName [session].[SessionName]
AS 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

View File

@@ -10,6 +10,6 @@ public interface IShogiApi
Task<ReadSessionsPlayerCountResponse?> GetSessionsPlayerCount(); Task<ReadSessionsPlayerCountResponse?> GetSessionsPlayerCount();
Task<CreateTokenResponse?> GetToken(); Task<CreateTokenResponse?> GetToken();
Task GuestLogout(); Task GuestLogout();
Task PostMove(string sessionName, Move move); Task PostMove(string sessionName, MovePieceCommand move);
Task<HttpStatusCode> PostSession(string name, bool isPrivate); Task<HttpStatusCode> PostSession(string name, bool isPrivate);
} }

View File

@@ -63,9 +63,9 @@ namespace Shogi.UI.Pages.Home.Api
return response; 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<HttpStatusCode> PostSession(string name, bool isPrivate) public async Task<HttpStatusCode> PostSession(string name, bool isPrivate)

View File

@@ -2,15 +2,21 @@
@inject IShogiApi ShogiApi @inject IShogiApi ShogiApi
@inject AccountState Account; @inject AccountState Account;
<section class="game-board" data-perspective="@Perspective"> <article class="game-board">
@for (var rank = 9; rank > 0; rank--) <!-- Game board -->
<section class="board" data-perspective="@Perspective">
@for (var rank = 1; rank < 10; rank++)
{ {
foreach (var file in Files) foreach (var file in Files)
{ {
var position = $"{file}{rank}"; var position = $"{file}{rank}";
var piece = session?.BoardState.Board[position]; var piece = session?.BoardState.Board[position];
<div class="tile" data-position="@(position)" style="grid-area: @(position)" @onclick="() => OnClickTile(piece, position)"> <div class="tile"
<GamePiece Piece="piece" Perspective="@Perspective" /> data-position="@(position)"
data-selected="@(piece != null && selectedPosition == position)"
style="grid-area: @(position)"
@onclick="() => OnClickTile(piece, position)">
<GamePiece Piece="piece" Perspective="Perspective" />
</div> </div>
} }
} }
@@ -36,16 +42,69 @@
<span>H</span> <span>H</span>
<span>I</span> <span>I</span>
</div> </div>
</section> </section>
<!-- Side board -->
@if (session != null)
{
<aside class="side-board">
<div class="hand">
@foreach (var piece in OpponentHand)
{
<div class="tile">
<GamePiece Piece="piece" Perspective="Perspective" />
</div>
}
</div>
<div class="spacer" />
<div class="hand">
@foreach (var piece in UserHand)
{
<div class="title" @onclick="() => OnClickHand(piece)">
<GamePiece Piece="piece" Perspective="Perspective" />
</div>
}
</div>
</aside>
}
</article>
@code { @code {
[Parameter] [Parameter]
public string? SessionName { get; set; } 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;
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; Session? session;
IReadOnlyCollection<Piece> OpponentHand
{
get
{
if (this.session == null) return Array.Empty<Piece>();
return Perspective == WhichPlayer.Player1
? this.session.BoardState.Player1Hand
: this.session.BoardState.Player2Hand;
}
}
IReadOnlyCollection<Piece> UserHand
{
get
{
if (this.session == null) return Array.Empty<Piece>();
return Perspective == WhichPlayer.Player1
? this.session.BoardState.Player1Hand
: this.session.BoardState.Player2Hand;
}
}
string? selectedPosition; string? selectedPosition;
WhichPiece? selectedPiece;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@@ -57,6 +116,28 @@
void OnClickTile(Piece? piece, string position) 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;
} }
} }

View File

@@ -1,5 +1,20 @@
.game-board { .game-board {
display: grid;
grid-template-areas: "board side-board";
grid-template-columns: 3fr minmax(10rem, 1fr);
gap: 0.5rem;
background-color: #444; background-color: #444;
}
.board {
grid-area: board;
}
.side-board {
grid-area: side-board;
}
.board {
display: grid; display: grid;
grid-template-areas: grid-template-areas:
"rank A9 B9 C9 D9 E9 F9 G9 H9 I9" "rank A9 B9 C9 D9 E9 F9 G9 H9 I9"
@@ -20,7 +35,7 @@
gap: 3px; gap: 3px;
} }
.game-board[data-perspective="Player2"] { .board[data-perspective="Player2"] {
grid-template-areas: grid-template-areas:
"file file file file file file file file file ." "file file file file file file file file file ."
"I1 H1 G1 F1 E1 D1 C1 B1 A1 rank" "I1 H1 G1 F1 E1 D1 C1 B1 A1 rank"
@@ -41,6 +56,11 @@
display: grid; display: grid;
place-content: center; place-content: center;
padding: 0.25rem; padding: 0.25rem;
overflow: hidden; /* Because SVGs are shaped weird */
transition: filter linear 0.25s;
}
.tile[data-selected] {
filter: invert(0.8);
} }
.ruler { .ruler {
@@ -54,10 +74,20 @@
flex-direction: column; flex-direction: column;
} }
.game-board[data-perspective="Player2"] .ruler { .board[data-perspective="Player2"] .ruler {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.game-board[data-perspective="Player2"] .ruler.vertical { .board[data-perspective="Player2"] .ruler.vertical {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
.side-board {
display: grid;
grid-template-rows: auto 1fr auto;
}
.side-board .hand {
display: flex;
flex-wrap: wrap;
}

View File

@@ -3,6 +3,7 @@
@using System.Net @using System.Net
@inject IShogiApi ShogiApi; @inject IShogiApi ShogiApi;
@inject ShogiSocket ShogiSocket; @inject ShogiSocket ShogiSocket;
@inject AccountState Account;
<section class="game-browser"> <section class="game-browser">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
@@ -76,8 +77,15 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
ShogiSocket.OnCreateGameMessage += async (sender, message) => await FetchSessions(); ShogiSocket.OnCreateGameMessage += async (sender, message) => await FetchSessions();
Account.LoginChangedEvent += async (sender, message) =>
{
if (message.User != null)
{
await FetchSessions(); await FetchSessions();
} }
};
}
string ActiveCss(SessionMetadata s) => s == activeSession ? "active" : string.Empty; string ActiveCss(SessionMetadata s) => s == activeSession ? "active" : string.Empty;
void OnClickSession(SessionMetadata s) void OnClickSession(SessionMetadata s)
@@ -92,6 +100,7 @@
if (sessions != null) if (sessions != null)
{ {
this.sessions = sessions.PlayerHasJoinedSessions.Concat(sessions.AllOtherSessions).ToArray(); this.sessions = sessions.PlayerHasJoinedSessions.Concat(sessions.AllOtherSessions).ToArray();
StateHasChanged();
} }
} }

View File

@@ -7,9 +7,8 @@ html, body, #app {
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: var(--primary-color); color: var(--primary-color);
font-family: cursive;
} }
span { span {

View File

@@ -1,3 +1,4 @@
using FluentAssertions.Execution;
using Shogi.AcceptanceTests.TestSetup; using Shogi.AcceptanceTests.TestSetup;
using Shogi.Contracts.Api; using Shogi.Contracts.Api;
using Shogi.Contracts.Types; using Shogi.Contracts.Types;
@@ -28,7 +29,7 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
try try
{ {
// Arrange // Arrange
await CreateSession(); await SetupTestSession();
// Act // Act
var readAllResponse = await Service var readAllResponse = await Service
@@ -45,17 +46,17 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
finally finally
{ {
// Annul // Annul
await DeleteSession(); await DeleteTestSession();
} }
} }
[Fact] [Fact]
public async Task CreateAndReadSession() public async Task CreateSession()
{ {
try try
{ {
// Arrange // Arrange
await CreateSession(); await SetupTestSession();
// Act // Act
var response = await Service.GetFromJsonAsync<ReadSessionResponse>( var response = await Service.GetFromJsonAsync<ReadSessionResponse>(
@@ -66,6 +67,7 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
response.Should().NotBeNull(); response.Should().NotBeNull();
response!.Session.Should().NotBeNull(); response!.Session.Should().NotBeNull();
response.Session.BoardState.Board.Should().NotBeEmpty(); response.Session.BoardState.Board.Should().NotBeEmpty();
ValidateBoard(response.Session.BoardState.Board);
response.Session.BoardState.Player1Hand.Should().BeEmpty(); response.Session.BoardState.Player1Hand.Should().BeEmpty();
response.Session.BoardState.Player2Hand.Should().BeEmpty(); response.Session.BoardState.Player2Hand.Should().BeEmpty();
response.Session.BoardState.PlayerInCheck.Should().BeNull(); response.Session.BoardState.PlayerInCheck.Should().BeNull();
@@ -77,11 +79,218 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
finally finally
{ {
// Annul // Annul
await DeleteSession(); await DeleteTestSession();
}
static void ValidateBoard(Dictionary<string, Piece?> 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( var createResponse = await Service.PostAsJsonAsync(
new Uri("Sessions", UriKind.Relative), new Uri("Sessions", UriKind.Relative),
@@ -90,10 +299,15 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
createResponse.StatusCode.Should().Be(HttpStatusCode.Created); createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
} }
private async Task DeleteSession() private Task<ReadSessionResponse> ReadTestSession()
{
return Service.GetFromJsonAsync<ReadSessionResponse>(new Uri("Sessions/Acceptance Tests", UriKind.Relative))!;
}
private async Task DeleteTestSession()
{ {
var response = await Service.DeleteAsync(new Uri("Sessions/Acceptance Tests", UriKind.Relative)); 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());
} }
} }

View File

@@ -9,75 +9,75 @@ public class ShogiBoardStateShould
var board = BoardState.StandardStarting; var board = BoardState.StandardStarting;
// Assert // Assert
board["A1"]?.WhichPiece.Should().Be(WhichPiece.Lance); board["A1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A1"]?.Owner.Should().Be(WhichPlayer.Player1); board["A1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A1"]?.IsPromoted.Should().Be(false); board["A1"]!.IsPromoted.Should().Be(false);
board["B1"]?.WhichPiece.Should().Be(WhichPiece.Knight); board["B1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B1"]?.Owner.Should().Be(WhichPlayer.Player1); board["B1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B1"]?.IsPromoted.Should().Be(false); board["B1"]!.IsPromoted.Should().Be(false);
board["C1"]?.WhichPiece.Should().Be(WhichPiece.SilverGeneral); board["C1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C1"]?.Owner.Should().Be(WhichPlayer.Player1); board["C1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C1"]?.IsPromoted.Should().Be(false); board["C1"]!.IsPromoted.Should().Be(false);
board["D1"]?.WhichPiece.Should().Be(WhichPiece.GoldGeneral); board["D1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D1"]?.Owner.Should().Be(WhichPlayer.Player1); board["D1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D1"]?.IsPromoted.Should().Be(false); board["D1"]!.IsPromoted.Should().Be(false);
board["E1"]?.WhichPiece.Should().Be(WhichPiece.King); board["E1"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E1"]?.Owner.Should().Be(WhichPlayer.Player1); board["E1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E1"]?.IsPromoted.Should().Be(false); board["E1"]!.IsPromoted.Should().Be(false);
board["F1"]?.WhichPiece.Should().Be(WhichPiece.GoldGeneral); board["F1"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F1"]?.Owner.Should().Be(WhichPlayer.Player1); board["F1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F1"]?.IsPromoted.Should().Be(false); board["F1"]!.IsPromoted.Should().Be(false);
board["G1"]?.WhichPiece.Should().Be(WhichPiece.SilverGeneral); board["G1"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G1"]?.Owner.Should().Be(WhichPlayer.Player1); board["G1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G1"]?.IsPromoted.Should().Be(false); board["G1"]!.IsPromoted.Should().Be(false);
board["H1"]?.WhichPiece.Should().Be(WhichPiece.Knight); board["H1"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H1"]?.Owner.Should().Be(WhichPlayer.Player1); board["H1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H1"]?.IsPromoted.Should().Be(false); board["H1"]!.IsPromoted.Should().Be(false);
board["I1"]?.WhichPiece.Should().Be(WhichPiece.Lance); board["I1"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I1"]?.Owner.Should().Be(WhichPlayer.Player1); board["I1"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I1"]?.IsPromoted.Should().Be(false); board["I1"]!.IsPromoted.Should().Be(false);
board["A2"].Should().BeNull(); board["A2"].Should().BeNull();
board["B2"]?.WhichPiece.Should().Be(WhichPiece.Bishop); board["B2"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["B2"]?.Owner.Should().Be(WhichPlayer.Player1); board["B2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B2"]?.IsPromoted.Should().Be(false); board["B2"]!.IsPromoted.Should().Be(false);
board["C2"].Should().BeNull(); board["C2"].Should().BeNull();
board["D2"].Should().BeNull(); board["D2"].Should().BeNull();
board["E2"].Should().BeNull(); board["E2"].Should().BeNull();
board["F2"].Should().BeNull(); board["F2"].Should().BeNull();
board["G2"].Should().BeNull(); board["G2"].Should().BeNull();
board["H2"]?.WhichPiece.Should().Be(WhichPiece.Rook); board["H2"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["H2"]?.Owner.Should().Be(WhichPlayer.Player1); board["H2"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H2"]?.IsPromoted.Should().Be(false); board["H2"]!.IsPromoted.Should().Be(false);
board["I2"].Should().BeNull(); board["I2"].Should().BeNull();
board["A3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["A3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A3"]?.Owner.Should().Be(WhichPlayer.Player1); board["A3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["A3"]?.IsPromoted.Should().Be(false); board["A3"]!.IsPromoted.Should().Be(false);
board["B3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["B3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B3"]?.Owner.Should().Be(WhichPlayer.Player1); board["B3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["B3"]?.IsPromoted.Should().Be(false); board["B3"]!.IsPromoted.Should().Be(false);
board["C3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["C3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C3"]?.Owner.Should().Be(WhichPlayer.Player1); board["C3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["C3"]?.IsPromoted.Should().Be(false); board["C3"]!.IsPromoted.Should().Be(false);
board["D3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["D3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D3"]?.Owner.Should().Be(WhichPlayer.Player1); board["D3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["D3"]?.IsPromoted.Should().Be(false); board["D3"]!.IsPromoted.Should().Be(false);
board["E3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["E3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E3"]?.Owner.Should().Be(WhichPlayer.Player1); board["E3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["E3"]?.IsPromoted.Should().Be(false); board["E3"]!.IsPromoted.Should().Be(false);
board["F3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["F3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F3"]?.Owner.Should().Be(WhichPlayer.Player1); board["F3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["F3"]?.IsPromoted.Should().Be(false); board["F3"]!.IsPromoted.Should().Be(false);
board["G3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["G3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G3"]?.Owner.Should().Be(WhichPlayer.Player1); board["G3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["G3"]?.IsPromoted.Should().Be(false); board["G3"]!.IsPromoted.Should().Be(false);
board["H3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["H3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H3"]?.Owner.Should().Be(WhichPlayer.Player1); board["H3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["H3"]?.IsPromoted.Should().Be(false); board["H3"]!.IsPromoted.Should().Be(false);
board["I3"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["I3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I3"]?.Owner.Should().Be(WhichPlayer.Player1); board["I3"]!.Owner.Should().Be(WhichPlayer.Player1);
board["I3"]?.IsPromoted.Should().Be(false); board["I3"]!.IsPromoted.Should().Be(false);
board["A4"].Should().BeNull(); board["A4"].Should().BeNull();
board["B4"].Should().BeNull(); board["B4"].Should().BeNull();
@@ -109,74 +109,74 @@ public class ShogiBoardStateShould
board["H6"].Should().BeNull(); board["H6"].Should().BeNull();
board["I6"].Should().BeNull(); board["I6"].Should().BeNull();
board["A7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["A7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["A7"]?.Owner.Should().Be(WhichPlayer.Player2); board["A7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A7"]?.IsPromoted.Should().Be(false); board["A7"]!.IsPromoted.Should().Be(false);
board["B7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["B7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["B7"]?.Owner.Should().Be(WhichPlayer.Player2); board["B7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B7"]?.IsPromoted.Should().Be(false); board["B7"]!.IsPromoted.Should().Be(false);
board["C7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["C7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["C7"]?.Owner.Should().Be(WhichPlayer.Player2); board["C7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C7"]?.IsPromoted.Should().Be(false); board["C7"]!.IsPromoted.Should().Be(false);
board["D7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["D7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["D7"]?.Owner.Should().Be(WhichPlayer.Player2); board["D7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D7"]?.IsPromoted.Should().Be(false); board["D7"]!.IsPromoted.Should().Be(false);
board["E7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["E7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["E7"]?.Owner.Should().Be(WhichPlayer.Player2); board["E7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E7"]?.IsPromoted.Should().Be(false); board["E7"]!.IsPromoted.Should().Be(false);
board["F7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["F7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["F7"]?.Owner.Should().Be(WhichPlayer.Player2); board["F7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F7"]?.IsPromoted.Should().Be(false); board["F7"]!.IsPromoted.Should().Be(false);
board["G7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["G7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["G7"]?.Owner.Should().Be(WhichPlayer.Player2); board["G7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G7"]?.IsPromoted.Should().Be(false); board["G7"]!.IsPromoted.Should().Be(false);
board["H7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["H7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["H7"]?.Owner.Should().Be(WhichPlayer.Player2); board["H7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H7"]?.IsPromoted.Should().Be(false); board["H7"]!.IsPromoted.Should().Be(false);
board["I7"]?.WhichPiece.Should().Be(WhichPiece.Pawn); board["I7"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
board["I7"]?.Owner.Should().Be(WhichPlayer.Player2); board["I7"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I7"]?.IsPromoted.Should().Be(false); board["I7"]!.IsPromoted.Should().Be(false);
board["A8"].Should().BeNull(); board["A8"].Should().BeNull();
board["B8"]?.WhichPiece.Should().Be(WhichPiece.Rook); board["B8"]!.WhichPiece.Should().Be(WhichPiece.Rook);
board["B8"]?.Owner.Should().Be(WhichPlayer.Player2); board["B8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B8"]?.IsPromoted.Should().Be(false); board["B8"]!.IsPromoted.Should().Be(false);
board["C8"].Should().BeNull(); board["C8"].Should().BeNull();
board["D8"].Should().BeNull(); board["D8"].Should().BeNull();
board["E8"].Should().BeNull(); board["E8"].Should().BeNull();
board["F8"].Should().BeNull(); board["F8"].Should().BeNull();
board["G8"].Should().BeNull(); board["G8"].Should().BeNull();
board["H8"]?.WhichPiece.Should().Be(WhichPiece.Bishop); board["H8"]!.WhichPiece.Should().Be(WhichPiece.Bishop);
board["H8"]?.Owner.Should().Be(WhichPlayer.Player2); board["H8"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H8"]?.IsPromoted.Should().Be(false); board["H8"]!.IsPromoted.Should().Be(false);
board["I8"].Should().BeNull(); board["I8"].Should().BeNull();
board["A9"]?.WhichPiece.Should().Be(WhichPiece.Lance); board["A9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["A9"]?.Owner.Should().Be(WhichPlayer.Player2); board["A9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["A9"]?.IsPromoted.Should().Be(false); board["A9"]!.IsPromoted.Should().Be(false);
board["B9"]?.WhichPiece.Should().Be(WhichPiece.Knight); board["B9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["B9"]?.Owner.Should().Be(WhichPlayer.Player2); board["B9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["B9"]?.IsPromoted.Should().Be(false); board["B9"]!.IsPromoted.Should().Be(false);
board["C9"]?.WhichPiece.Should().Be(WhichPiece.SilverGeneral); board["C9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["C9"]?.Owner.Should().Be(WhichPlayer.Player2); board["C9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["C9"]?.IsPromoted.Should().Be(false); board["C9"]!.IsPromoted.Should().Be(false);
board["D9"]?.WhichPiece.Should().Be(WhichPiece.GoldGeneral); board["D9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["D9"]?.Owner.Should().Be(WhichPlayer.Player2); board["D9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["D9"]?.IsPromoted.Should().Be(false); board["D9"]!.IsPromoted.Should().Be(false);
board["E9"]?.WhichPiece.Should().Be(WhichPiece.King); board["E9"]!.WhichPiece.Should().Be(WhichPiece.King);
board["E9"]?.Owner.Should().Be(WhichPlayer.Player2); board["E9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["E9"]?.IsPromoted.Should().Be(false); board["E9"]!.IsPromoted.Should().Be(false);
board["F9"]?.WhichPiece.Should().Be(WhichPiece.GoldGeneral); board["F9"]!.WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board["F9"]?.Owner.Should().Be(WhichPlayer.Player2); board["F9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["F9"]?.IsPromoted.Should().Be(false); board["F9"]!.IsPromoted.Should().Be(false);
board["G9"]?.WhichPiece.Should().Be(WhichPiece.SilverGeneral); board["G9"]!.WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board["G9"]?.Owner.Should().Be(WhichPlayer.Player2); board["G9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["G9"]?.IsPromoted.Should().Be(false); board["G9"]!.IsPromoted.Should().Be(false);
board["H9"]?.WhichPiece.Should().Be(WhichPiece.Knight); board["H9"]!.WhichPiece.Should().Be(WhichPiece.Knight);
board["H9"]?.Owner.Should().Be(WhichPlayer.Player2); board["H9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["H9"]?.IsPromoted.Should().Be(false); board["H9"]!.IsPromoted.Should().Be(false);
board["I9"]?.WhichPiece.Should().Be(WhichPiece.Lance); board["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance);
board["I9"]?.Owner.Should().Be(WhichPlayer.Player2); board["I9"]!.Owner.Should().Be(WhichPlayer.Player2);
board["I9"]?.IsPromoted.Should().Be(false); board["I9"]!.IsPromoted.Should().Be(false);
} }
} }