diff --git a/Shogi.Api/Extensions/ContractsExtensions.cs b/Shogi.Api/Extensions/ContractsExtensions.cs deleted file mode 100644 index 71321e0..0000000 --- a/Shogi.Api/Extensions/ContractsExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Shogi.Contracts.Types; -using System.Collections.ObjectModel; - -namespace Shogi.Api.Extensions; - -public static class ContractsExtensions -{ - public static WhichPlayer ToContract(this Domain.ValueObjects.WhichPlayer player) - { - return player switch - { - Domain.ValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1, - Domain.ValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2, - _ => throw new NotImplementedException(), - }; - } - - public static WhichPiece ToContract(this Domain.ValueObjects.WhichPiece piece) - { - return piece switch - { - Domain.ValueObjects.WhichPiece.King => WhichPiece.King, - Domain.ValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral, - Domain.ValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral, - Domain.ValueObjects.WhichPiece.Bishop => WhichPiece.Bishop, - Domain.ValueObjects.WhichPiece.Rook => WhichPiece.Rook, - Domain.ValueObjects.WhichPiece.Knight => WhichPiece.Knight, - Domain.ValueObjects.WhichPiece.Lance => WhichPiece.Lance, - Domain.ValueObjects.WhichPiece.Pawn => WhichPiece.Pawn, - _ => throw new NotImplementedException(), - }; - } - - public static Piece ToContract(this Domain.ValueObjects.Piece piece) => new() - { - IsPromoted = piece.IsPromoted, - Owner = piece.Owner.ToContract(), - WhichPiece = piece.WhichPiece.ToContract() - }; - - public static IReadOnlyList ToContract(this List pieces) - { - return pieces - .Select(ToContract) - .ToList() - .AsReadOnly(); - } - - public static Dictionary ToContract(this ReadOnlyDictionary boardState) => - boardState.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToContract()); - - public static Domain.ValueObjects.WhichPiece ToDomain(this WhichPiece piece) - { - return piece switch - { - WhichPiece.King => Domain.ValueObjects.WhichPiece.King, - WhichPiece.GoldGeneral => Domain.ValueObjects.WhichPiece.GoldGeneral, - WhichPiece.SilverGeneral => Domain.ValueObjects.WhichPiece.SilverGeneral, - WhichPiece.Bishop => Domain.ValueObjects.WhichPiece.Bishop, - WhichPiece.Rook => Domain.ValueObjects.WhichPiece.Rook, - WhichPiece.Knight => Domain.ValueObjects.WhichPiece.Knight, - WhichPiece.Lance => Domain.ValueObjects.WhichPiece.Lance, - WhichPiece.Pawn => Domain.ValueObjects.WhichPiece.Pawn, - _ => throw new NotImplementedException(), - }; - } -} diff --git a/Shogi.Api/Program.cs b/Shogi.Api/Program.cs deleted file mode 100644 index 673199a..0000000 --- a/Shogi.Api/Program.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.AspNetCore.Identity.UI.Services; -using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.EntityFrameworkCore; -using Shogi.Api; -using Shogi.Api.Application; -using Shogi.Api.Controllers; -using Shogi.Api.Identity; -using Shogi.Api.Repositories; - -var builder = WebApplication.CreateBuilder(args); -var allowedOrigins = builder - .Configuration - .GetSection("Cors:AllowedOrigins") - .Get() ?? throw new InvalidOperationException("Configuration for allowed origins is missing."); - -builder.Services - .AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.WriteIndented = true; - }); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); -builder.Services.AddTransient(); -builder.Services.AddTransient(); -builder.Services.AddTransient(); -builder.Services.AddTransient(); -builder.Services.AddHttpClient(); -builder.Services.Configure(builder.Configuration.GetSection("ApiKeys")); - -AddIdentity(builder, builder.Configuration); -builder.Services.AddSignalR(); -builder.Services.AddResponseCompression(opts => -{ - opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]); -}); -var app = builder.Build(); - -app.MyMapIdentityApi(builder.Environment); - -if (app.Environment.IsDevelopment()) -{ - app.UseHttpsRedirection(); // Apache handles HTTPS in production. -} -else -{ - app.UseResponseCompression(); -} -app.UseSwagger(); -app.UseSwaggerUI(options => options.DocumentTitle = "Shogi.Api"); -app.UseAuthorization(); -app.Map("/", () => "OK"); -app.MapControllers(); -app.UseCors(policy => -{ - policy.WithOrigins(allowedOrigins).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); -}); - -app.MapHub("/gamehub"); - -app.Run(); - -static void AddIdentity(WebApplicationBuilder builder, ConfigurationManager configuration) -{ - builder.Services - .AddAuthorizationBuilder() - .AddPolicy("Admin", policy => - { - policy.RequireAuthenticatedUser(); - policy.RequireAssertion(context => context.User?.Identity?.Name switch - { - "Hauth@live.com" => true, - "aat-account" => true, - _ => false - }); - }); - - builder.Services - .AddDbContext(options => - { - var cs = configuration.GetConnectionString("ShogiDatabase") ?? throw new InvalidOperationException("Database not configured."); - options.UseSqlServer(cs); - - // This is helpful to debug account issues without affecting the database. - //options.UseInMemoryDatabase("AppDb"); - }) - .AddIdentityApiEndpoints(options => - { - options.SignIn.RequireConfirmedEmail = true; - options.User.RequireUniqueEmail = true; - }) - .AddEntityFrameworkStores(); - - builder.Services.ConfigureApplicationCookie(options => - { - options.SlidingExpiration = true; - options.ExpireTimeSpan = TimeSpan.FromDays(3); - }); - -} \ No newline at end of file diff --git a/Shogi.Api/Properties/launchSettings.json b/Shogi.Api/Properties/launchSettings.json deleted file mode 100644 index 0851a5a..0000000 --- a/Shogi.Api/Properties/launchSettings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "profiles": { - "Kestrel": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "VaultUri": "https://gameboardshogiuisocketsv.vault.azure.net/", - "AZURE_USERNAME": "Hauth@live.com" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:50728/", - "sslPort": 44315 - } - } -} \ No newline at end of file diff --git a/Shogi.Api/Shogi.Api.csproj b/Shogi.Api/Shogi.Api.csproj deleted file mode 100644 index 499f7e4..0000000 --- a/Shogi.Api/Shogi.Api.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - net10.0 - true - 5 - enable - False - False - enable - 973a1f5f-ef25-4f1c-a24d-b0fc7d016ab8 - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - diff --git a/Shogi.Contracts/Shogi.Contracts.csproj b/Shogi.Contracts/Shogi.Contracts.csproj deleted file mode 100644 index 8ca8e19..0000000 --- a/Shogi.Contracts/Shogi.Contracts.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net10.0 - true - 5 - enable - False - Shogi Service Models - Contains DTOs use for http requests to Shogi backend services. - - - - - - - diff --git a/Shogi.Contracts/ShogiApiJsonSerializerSettings.cs b/Shogi.Contracts/ShogiApiJsonSerializerSettings.cs deleted file mode 100644 index 89277f8..0000000 --- a/Shogi.Contracts/ShogiApiJsonSerializerSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json; - -namespace Shogi.Contracts; - -public class ShogiApiJsonSerializerSettings -{ - public readonly static JsonSerializerOptions SystemTextJsonSerializerOptions = new(JsonSerializerDefaults.Web) - { - WriteIndented = true, - }; -} diff --git a/Shogi.Contracts/Types/Piece.cs b/Shogi.Contracts/Types/Piece.cs deleted file mode 100644 index 3c7b335..0000000 --- a/Shogi.Contracts/Types/Piece.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Shogi.Contracts.Types -{ - public class Piece - { - public bool IsPromoted { get; set; } - public WhichPiece WhichPiece { get; set; } - public WhichPlayer Owner { get; set; } - } -} diff --git a/Shogi.Database/FirstTimeSetup.sql b/Shogi.Database/FirstTimeSetup.sql index 5dfe63b..b61353c 100644 --- a/Shogi.Database/FirstTimeSetup.sql +++ b/Shogi.Database/FirstTimeSetup.sql @@ -1,10 +1,10 @@ --- Create a user named Shogi.Api +-- Create a user named Shogi -- Create a role and grant execute permission to that role --CREATE ROLE db_executor --GRANT EXECUTE To db_executor --- Give Shogi.Api user permission to db_executor, db_datareader, db_datawriter +-- Give Shogi user permission to db_executor, db_datareader, db_datawriter /** @@ -13,5 +13,5 @@ * * 2. Setup Entity Framework because that's what the login system uses. * 2.a. Install the Entity Framework dotnet tools, via power shell run this command: dotnet tool install --global dotnet-ef -* 2.b. To setup the Entity Framework users database, run this powershell command using Shogi.Api as the target project: dotnet ef database update +* 2.b. To setup the Entity Framework users database, run this powershell command using Shogi as the target project: dotnet ef database update */ \ No newline at end of file diff --git a/Shogi.Domain/Shogi.Domain.csproj b/Shogi.Domain/Shogi.Domain.csproj deleted file mode 100644 index ca661e0..0000000 --- a/Shogi.Domain/Shogi.Domain.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net10.0 - disable - enable - - - - - - - - - - - - - - diff --git a/Shogi.Domain/ValueObjects/Movement/Direction.cs b/Shogi.Domain/ValueObjects/Movement/Direction.cs deleted file mode 100644 index e2e71c8..0000000 --- a/Shogi.Domain/ValueObjects/Movement/Direction.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Shogi.Domain.ValueObjects; - -public static class Direction - { - public static readonly Vector2 Forward = new(0, 1); - public static readonly Vector2 Backward = new(0, -1); - public static readonly Vector2 Left = new(-1, 0); - public static readonly Vector2 Right = new(1, 0); - public static readonly Vector2 ForwardLeft = new(-1, 1); - public static readonly Vector2 ForwardRight = new(1, 1); - public static readonly Vector2 BackwardLeft = new(-1, -1); - public static readonly Vector2 BackwardRight = new(1, -1); - public static readonly Vector2 KnightLeft = new(-1, 2); - public static readonly Vector2 KnightRight = new(1, 2); - } diff --git a/Shogi.Domain/ValueObjects/Movement/MoveResult.cs b/Shogi.Domain/ValueObjects/Movement/MoveResult.cs deleted file mode 100644 index f66140f..0000000 --- a/Shogi.Domain/ValueObjects/Movement/MoveResult.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Shogi.Domain.ValueObjects -{ - public record MoveResult(bool IsSuccess, string Reason = "") - { - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/Bishop.cs b/Shogi.Domain/ValueObjects/Pieces/Bishop.cs deleted file mode 100644 index 4e721f4..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/Bishop.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Collections.ObjectModel; - -namespace Shogi.Domain.ValueObjects -{ - internal record class Bishop : Piece - { - private static readonly ReadOnlyCollection BishopPaths = new(new List(4) - { - new Path(Direction.ForwardLeft, Distance.MultiStep), - new Path(Direction.ForwardRight, Distance.MultiStep), - new Path(Direction.BackwardLeft, Distance.MultiStep), - new Path(Direction.BackwardRight, Distance.MultiStep) - }); - - public static readonly ReadOnlyCollection PromotedBishopPaths = new(new List(8) - { - new Path(Direction.Forward), - new Path(Direction.Left), - new Path(Direction.Right), - new Path(Direction.Backward), - new Path(Direction.ForwardLeft, Distance.MultiStep), - new Path(Direction.ForwardRight, Distance.MultiStep), - new Path(Direction.BackwardLeft, Distance.MultiStep), - new Path(Direction.BackwardRight, Distance.MultiStep) - }); - - public static readonly ReadOnlyCollection Player2Paths = - BishopPaths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public static readonly ReadOnlyCollection Player2PromotedPaths = - PromotedBishopPaths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public Bishop(WhichPlayer owner, bool isPromoted = false) - : base(WhichPiece.Bishop, owner, isPromoted) - { - } - - public override IEnumerable MoveSet => IsPromoted ? PromotedBishopPaths : BishopPaths; - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/Knight.cs b/Shogi.Domain/ValueObjects/Pieces/Knight.cs deleted file mode 100644 index acccfb8..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/Knight.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Collections.ObjectModel; - -namespace Shogi.Domain.ValueObjects -{ - internal record class Knight : Piece - { - public static readonly ReadOnlyCollection Player1Paths = new(new List(2) - { - new Path(Direction.KnightLeft), - new Path(Direction.KnightRight) - }); - - public static readonly ReadOnlyCollection Player2Paths = - Player1Paths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public Knight(WhichPlayer owner, bool isPromoted = false) - : base(WhichPiece.Knight, owner, isPromoted) - { - } - - public override ReadOnlyCollection MoveSet => Owner switch - { - WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, - WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, - _ => throw new NotImplementedException(), - }; - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/Lance.cs b/Shogi.Domain/ValueObjects/Pieces/Lance.cs deleted file mode 100644 index aaf54e9..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/Lance.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Collections.ObjectModel; - -namespace Shogi.Domain.ValueObjects -{ - internal record class Lance : Piece - { - public static readonly ReadOnlyCollection Player1Paths = new(new List(1) - { - new Path(Direction.Forward, Distance.MultiStep), - }); - - public static readonly ReadOnlyCollection Player2Paths = - Player1Paths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public Lance(WhichPlayer owner, bool isPromoted = false) - : base(WhichPiece.Lance, owner, isPromoted) - { - } - - public override ReadOnlyCollection MoveSet => Owner switch - { - WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, - WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, - _ => throw new NotImplementedException(), - }; - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/Pawn.cs b/Shogi.Domain/ValueObjects/Pieces/Pawn.cs deleted file mode 100644 index 7ce3e22..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/Pawn.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Collections.ObjectModel; - -namespace Shogi.Domain.ValueObjects -{ - internal record class Pawn : Piece - { - public static readonly ReadOnlyCollection Player1Paths = new(new List(1) - { - new Path(Direction.Forward) - }); - - public static readonly ReadOnlyCollection Player2Paths = - Player1Paths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public Pawn(WhichPlayer owner, bool isPromoted = false) - : base(WhichPiece.Pawn, owner, isPromoted) - { - } - - public override ReadOnlyCollection MoveSet => Owner switch - { - WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, - WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, - _ => throw new NotImplementedException(), - }; - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/Piece.cs b/Shogi.Domain/ValueObjects/Pieces/Piece.cs deleted file mode 100644 index d57e8ec..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/Piece.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Diagnostics; - -namespace Shogi.Domain.ValueObjects -{ - [DebuggerDisplay("{WhichPiece} {Owner}")] - public abstract record class Piece - { - public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) - { - return piece switch - { - WhichPiece.King => new King(owner, isPromoted), - WhichPiece.GoldGeneral => new GoldGeneral(owner, isPromoted), - WhichPiece.SilverGeneral => new SilverGeneral(owner, isPromoted), - WhichPiece.Bishop => new Bishop(owner, isPromoted), - WhichPiece.Rook => new Rook(owner, isPromoted), - WhichPiece.Knight => new Knight(owner, isPromoted), - WhichPiece.Lance => new Lance(owner, isPromoted), - WhichPiece.Pawn => new Pawn(owner, isPromoted), - _ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.") - }; - } - public abstract IEnumerable MoveSet { get; } - public WhichPiece WhichPiece { get; } - public WhichPlayer Owner { get; private set; } - public bool IsPromoted { get; private set; } - protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) - { - WhichPiece = piece; - Owner = owner; - IsPromoted = isPromoted; - } - - public bool CanPromote => !IsPromoted - && WhichPiece != WhichPiece.King - && WhichPiece != WhichPiece.GoldGeneral; - - public void Promote() => IsPromoted = CanPromote; - - /// - /// Prep the piece for capture by changing ownership and demoting. - /// - public void Capture(WhichPlayer newOwner) - { - Owner = newOwner; - IsPromoted = false; - } - - /// - /// Respecting the move-set of the Piece, collect all positions along the shortest path from start to end. - /// Useful if you need to iterate a move-set. - /// - /// - /// - /// An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions. - public IEnumerable GetPathFromStartToEnd(Vector2 start, Vector2 end) - { - var steps = new List(10); - - var path = GetNearestPath(MoveSet, start, end); - var position = start; - while (Vector2.Distance(start, position) < Vector2.Distance(start, end)) - { - position += path.Step; - steps.Add(position); - - if (path.Distance == Distance.OneStep) break; - } - - if (position == end) - { - return steps; - } - - return []; - } - - private static Path GetNearestPath(IEnumerable paths, Vector2 start, Vector2 end) - { - if (!paths.DefaultIfEmpty().Any()) - { - throw new ArgumentException("No paths to get nearest path from."); - } - - var shortestPath = paths.First(); - foreach (var path in paths.Skip(1)) - { - var distance = Vector2.Distance(start + path.Step, end); - var shortestDistance = Vector2.Distance(start + shortestPath.Step, end); - if (distance < shortestDistance) - { - shortestPath = path; - } - } - return shortestPath; - } - } -} diff --git a/Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs b/Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs deleted file mode 100644 index 82c5519..0000000 --- a/Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Shogi.Domain.ValueObjects.Movement; -using System.Collections.ObjectModel; - -namespace Shogi.Domain.ValueObjects -{ - internal record class SilverGeneral : Piece - { - public static readonly ReadOnlyCollection Player1Paths = new(new List(4) - { - new Path(Direction.Forward), - new Path(Direction.ForwardLeft), - new Path(Direction.ForwardRight), - new Path(Direction.BackwardLeft), - new Path(Direction.BackwardRight) - }); - - public static readonly ReadOnlyCollection Player2Paths = - Player1Paths - .Select(p => p.Invert()) - .ToList() - .AsReadOnly(); - - public SilverGeneral(WhichPlayer owner, bool isPromoted = false) - : base(WhichPiece.SilverGeneral, owner, isPromoted) - { - } - - public override ReadOnlyCollection MoveSet => Owner switch - { - WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, - WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, - _ => throw new NotImplementedException(), - }; - } -} diff --git a/Shogi.Domain/ValueObjects/Rules/StandardRules.cs b/Shogi.Domain/ValueObjects/Rules/StandardRules.cs deleted file mode 100644 index 911520a..0000000 --- a/Shogi.Domain/ValueObjects/Rules/StandardRules.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD; -using BoardTile = System.Collections.Generic.KeyValuePair; - -namespace Shogi.Domain.ValueObjects -{ - internal class StandardRules - { - private readonly BoardState boardState; - - internal StandardRules(BoardState board) - { - boardState = board; - } - - /// - /// Determines if the last move put the player who moved in check. - /// - /// - /// This strategy recognizes that a "discover check" could only occur from a subset of pieces: Rook, Bishop, Lance. - /// In this way, only those pieces need to be considered when evaluating if a move placed the moving player in check. - /// - internal bool DidPlayerPutThemselfInCheck() - { - if (boardState.PreviousMove.From == null) - { - // You can't place yourself in check by placing a piece from your hand. - return false; - } - - var previousMovedPiece = boardState[boardState.PreviousMove.To]; - if (previousMovedPiece == null) throw new ArgumentNullException(nameof(previousMovedPiece), $"No piece exists at position {boardState.PreviousMove.To}."); - var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition; - - - var isDiscoverCheck = false; - // Get line equation from king through the now-unoccupied location. - var direction = Vector2.Subtract(kingPosition, boardState.PreviousMove.From.Value); - var slope = Math.Abs(direction.Y / direction.X); - var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMove.From.Value, Vector2.Normalize(direction)); - var threat = boardState.QueryFirstPieceInPath(path); - if (threat == null || threat.Owner == previousMovedPiece.Owner) return false; - // 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)) - { - isDiscoverCheck = threat.WhichPiece switch - { - WhichPiece.Lance => !threat.IsPromoted, - WhichPiece.Rook => true, - _ => false - }; - } - else if (slope == 1) - { - isDiscoverCheck = threat.WhichPiece switch - { - WhichPiece.Bishop => true, - _ => false - }; - } - else if (slope == 0) - { - isDiscoverCheck = threat.WhichPiece switch - { - WhichPiece.Rook => true, - _ => false - }; - } - return isDiscoverCheck; - } - - internal bool IsOpponentInCheckAfterMove() => IsOpposingKingThreatenedByPosition(boardState.PreviousMove.To); - - internal bool IsOpposingKingThreatenedByPosition(Vector2 position) - { - var previousMovedPiece = boardState[position]; - if (previousMovedPiece == null) return false; - - var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition; - var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition); - var threatenedPiece = boardState.QueryFirstPieceInPath(path); - if (!path.Any() || threatenedPiece == null) return false; - - return threatenedPiece.WhichPiece == WhichPiece.King; - } - - internal bool IsOpponentInCheckMate() - { - // Assume checkmate, then try to disprove. - if (!boardState.InCheck.HasValue) return false; - // Get all pieces from opponent who threaten the king in question. - var opponent = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; - var tilesOccupiedByOpponent = boardState.GetTilesOccupiedBy(opponent); - var kingPosition = boardState.WhoseTurn == WhichPlayer.Player1 - ? boardState.Player1KingPosition - : boardState.Player2KingPosition; - var threats = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList(); - if (threats.Count == 1) - { - /* If there is exactly one threat it is possible to block the check. - * Foreach piece owned by whichPlayer - * if piece can intercept check, return false; - */ - var threat = threats.Single(); - var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition); - var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn); - foreach (var threatBlockingPosition in pathFromThreatToKing) - { - var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat - .Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition)) - .ToList(); - - if (tilesThatDoBlockThreat.Any()) - { - return false; // Cannot be check-mate if a piece can intercept the threat. - } - } - } - else - { - /* - * If no ability to block the check, maybe the king can evade check by moving. - */ - - foreach (var maybeSafePosition in GetPossiblePositionsForKing(boardState.WhoseTurn)) - { - threats = tilesOccupiedByOpponent - .Where(tile => PieceHasLineOfSight(tile, maybeSafePosition)) - .ToList(); - if (!threats.Any()) - { - return false; - } - } - - } - - return true; - } - - private List GetPossiblePositionsForKing(WhichPlayer whichPlayer) - { - var kingPosition = whichPlayer == WhichPlayer.Player1 - ? boardState.Player1KingPosition - : boardState.Player2KingPosition; - - var paths = boardState[kingPosition]!.MoveSet; - return paths - .Select(path => path.Step + kingPosition) - // Because the king could be on the edge of the board, where some of its paths do not make sense. - .Where(newPosition => newPosition.IsInsideBoardBoundary()) - // Where tile at position is empty, meaning the king could move there. - .Where(newPosition => boardState[newPosition] == null) - .ToList(); - } - - private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget) - { - var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget); - return path - .SkipLast(1) - .All(position => boardState[Notation.ToBoardNotation(position)] == null); - } - } -} diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/DomainExtensions.cs b/Shogi.Domain/YetToBeAssimilatedIntoDDD/DomainExtensions.cs deleted file mode 100644 index dd5c72a..0000000 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/DomainExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Shogi.Domain.ValueObjects; - -namespace Shogi.Domain.YetToBeAssimilatedIntoDDD -{ - internal static class DomainExtensions - { - public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King; - - public static bool IsBetween(this float self, float min, float max) - { - return self >= min && self <= max; - } - - public static bool IsInsideBoardBoundary(this Vector2 self) - { - return self.X.IsBetween(0, 8) && self.Y.IsBetween(0, 8); - } - } -} diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Notation.cs b/Shogi.Domain/YetToBeAssimilatedIntoDDD/Notation.cs deleted file mode 100644 index f75600e..0000000 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Notation.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Shogi.Domain.YetToBeAssimilatedIntoDDD -{ - public static class Notation - { - private static readonly string BoardNotationRegex = @"(?[A-I])(?[1-9])"; - private static readonly char A = 'A'; - - public static string ToBoardNotation(Vector2 vector) - { - return ToBoardNotation((int)vector.X, (int)vector.Y); - } - - public static string ToBoardNotation(int x, int y) - { - var file = (char)(x + A); - var rank = y + 1; - return $"{file}{rank}"; - } - public static Vector2 FromBoardNotation(string notation) - { - if (Regex.IsMatch(notation, BoardNotationRegex)) - { - var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase); - char file = match.Groups["file"].Value[0]; - int rank = int.Parse(match.Groups["rank"].Value); - return new Vector2(file - A, rank - 1); - } - throw new ArgumentException($"Board notation not recognized. Notation given: {notation}"); - } - } -} diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/ReadMe.md b/Shogi.Domain/YetToBeAssimilatedIntoDDD/ReadMe.md deleted file mode 100644 index 51bfb0f..0000000 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/ReadMe.md +++ /dev/null @@ -1,4 +0,0 @@ -# Shogi.Domain - -### TODO: -* There are enough non-DDD classes around navigating the standard 9x9 Shogi board that probably a new value object or entity is merited. See classes within Pathing folder as well as Notation.cs. \ No newline at end of file diff --git a/Shogi.UI/.config/dotnet-tools.json b/Shogi.UI/.config/dotnet-tools.json deleted file mode 100644 index 4b64fb7..0000000 --- a/Shogi.UI/.config/dotnet-tools.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "microsoft.dotnet-msidentity": { - "version": "1.0.5", - "commands": [ - "dotnet-msidentity" - ] - } - } -} \ No newline at end of file diff --git a/Shogi.UI/Identity/CookieMessageHandler.cs b/Shogi.UI/Identity/CookieMessageHandler.cs deleted file mode 100644 index 3d78081..0000000 --- a/Shogi.UI/Identity/CookieMessageHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Http; - -namespace Shogi.UI.Identity; - -public class CookieCredentialsMessageHandler : DelegatingHandler -{ - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); - request.Headers.Add("X-Requested-With", ["XMLHttpRequest"]); - - return base.SendAsync(request, cancellationToken); - } -} diff --git a/Shogi.UI/Pages/Home/_Imports.razor b/Shogi.UI/Pages/Home/_Imports.razor deleted file mode 100644 index 0a497b0..0000000 --- a/Shogi.UI/Pages/Home/_Imports.razor +++ /dev/null @@ -1 +0,0 @@ -@using Shogi.UI.Pages.Home.VisualAids \ No newline at end of file diff --git a/Shogi.UI/Pages/Play/GameBoard/EmptyGameBoard.razor b/Shogi.UI/Pages/Play/GameBoard/EmptyGameBoard.razor deleted file mode 100644 index 0473868..0000000 --- a/Shogi.UI/Pages/Play/GameBoard/EmptyGameBoard.razor +++ /dev/null @@ -1,6 +0,0 @@ -@using Contracts.Types; - - - -@code { -} diff --git a/Shogi.UI/Program.cs b/Shogi.UI/Program.cs deleted file mode 100644 index 9dd0486..0000000 --- a/Shogi.UI/Program.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.AspNetCore.ResponseCompression; -using Shogi.UI; -using Shogi.UI.Identity; -using Shogi.UI.Shared; -using System.Text.Json; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); -builder.Logging.AddConfiguration( - builder.Configuration.GetSection("Logging")); -ConfigureDependencies(builder.Services, builder.Configuration); - -builder.Services.AddResponseCompression(options => -{ - options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]); -}); - -await builder.Build().RunAsync(); - -static void ConfigureDependencies(IServiceCollection services, IConfiguration configuration) -{ - /** - * Why two HTTP clients? - * See https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?source=recommendations&view=aspnetcore-6.0#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client - */ - var baseUrl = configuration["ShogiApiUrl"]; - if (string.IsNullOrWhiteSpace(baseUrl)) - { - throw new InvalidOperationException("ShogiApiUrl configuration is missing."); - } - - var shogiApiUrl = new Uri(baseUrl, UriKind.Absolute); - - services - .AddTransient() - .AddTransient(); - - // Identity - services - .AddAuthorizationCore(options => options.AddPolicy("Admin", policy => policy.RequireUserName("Hauth@live.com"))) - .AddScoped() - .AddScoped(sp => (IAccountManagement)sp.GetRequiredService()) - .AddHttpClient("Auth", client => client.BaseAddress = shogiApiUrl) // "Auth" is the name expected by the auth library. - .AddHttpMessageHandler(); - - // Network clients - services - .AddHttpClient(client => client.BaseAddress = shogiApiUrl) - .AddHttpMessageHandler(); - services - .AddSingleton(); - - - var serializerOptions = new JsonSerializerOptions - { - WriteIndented = true - }; - services.AddSingleton((sp) => serializerOptions); -} \ No newline at end of file diff --git a/Shogi.UI/Properties/Resources.Designer.cs b/Shogi.UI/Properties/Resources.Designer.cs deleted file mode 100644 index 59e260a..0000000 --- a/Shogi.UI/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Shogi.UI.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shogi.UI.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/Shogi.UI/Properties/Resources.resx b/Shogi.UI/Properties/Resources.resx deleted file mode 100644 index 4fdb1b6..0000000 --- a/Shogi.UI/Properties/Resources.resx +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Shogi.UI/Properties/launchSettings.json b/Shogi.UI/Properties/launchSettings.json deleted file mode 100644 index 8c7658c..0000000 --- a/Shogi.UI/Properties/launchSettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "profiles": { - "Shogi.UI": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:3000", - "nativeDebugging": true - } - } -} \ No newline at end of file diff --git a/Shogi.UI/Properties/serviceDependencies.json b/Shogi.UI/Properties/serviceDependencies.json deleted file mode 100644 index 44cc45e..0000000 --- a/Shogi.UI/Properties/serviceDependencies.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "identityapp1": { - "type": "identityapp", - "dynamicId": null - } - } -} \ No newline at end of file diff --git a/Shogi.UI/Properties/serviceDependencies.local.json b/Shogi.UI/Properties/serviceDependencies.local.json deleted file mode 100644 index 3c85224..0000000 --- a/Shogi.UI/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "identityapp1": { - "type": "identityapp.default", - "dynamicId": null - } - } -} \ No newline at end of file diff --git a/Shogi.UI/Shared/ShogiApi.cs b/Shogi.UI/Shared/ShogiApi.cs deleted file mode 100644 index 34cb91e..0000000 --- a/Shogi.UI/Shared/ShogiApi.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Shogi.Contracts.Api.Commands; -using Shogi.Contracts.Types; -using System.Net; -using System.Net.Http.Json; -using System.Reflection.Metadata.Ecma335; -using System.Text.Json; - -namespace Shogi.UI.Shared; - -public class ShogiApi(HttpClient httpClient) -{ - private readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); - - public async Task Register(string email, string password) - { - var response = await httpClient.PostAsJsonAsync(Relative("register"), new { email, password }); - response.EnsureSuccessStatusCode(); - } - - public async Task LoginEventArgs(string email, string password) - { - var response = await httpClient.PostAsJsonAsync("login?useCookies=true", new { email, password }); - response.EnsureSuccessStatusCode(); - } - - public async Task Logout() - { - var response = await httpClient.PutAsync(Relative("User/GuestLogout"), null); - response.EnsureSuccessStatusCode(); - } - - public async Task GetSession(string name) - { - var response = await httpClient.GetAsync(Relative($"Sessions/{name}")); - if (response.IsSuccessStatusCode) - { - return (await response.Content.ReadFromJsonAsync(this.serializerOptions)); - } - return null; - } - - public async Task GetAllSessionsMetadata() - { - var response = await httpClient.GetAsync(Relative("Sessions")); - if (response.IsSuccessStatusCode) - { - return (await response.Content.ReadFromJsonAsync(this.serializerOptions))!; - } - return []; - } - - /// - /// Returns false if the move was not accepted by the server. - /// - public async Task Move(Guid sessionName, MovePieceCommand command) - { - var response = await httpClient.PatchAsync(Relative($"Sessions/{sessionName}/Move"), JsonContent.Create(command)); - return response.IsSuccessStatusCode; - } - - public async Task PostSession() - { - var response = await httpClient.PostAsync(Relative("Sessions"), null); - var sessionId = response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : null; - return sessionId; - } - - public Task PatchJoinGame(string name) - { - return httpClient.PatchAsync(Relative($"Sessions/{name}/Join"), null); - } - - public Task DeleteSession(Guid sessionId) - { - return httpClient.DeleteAsync(Relative($"Sessions/{sessionId}")); - } - - private static Uri Relative(string path) => new(path, UriKind.Relative); -} diff --git a/Shogi.UI/Shogi.UI.csproj b/Shogi.UI/Shogi.UI.csproj deleted file mode 100644 index 515869a..0000000 --- a/Shogi.UI/Shogi.UI.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - net10.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - diff --git a/Shogi.UI/wwwroot/appsettings.Development.json b/Shogi.UI/wwwroot/appsettings.Development.json deleted file mode 100644 index 2c63c08..0000000 --- a/Shogi.UI/wwwroot/appsettings.Development.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/Shogi.UI/wwwroot/appsettings.json b/Shogi.UI/wwwroot/appsettings.json deleted file mode 100644 index 12680a0..0000000 --- a/Shogi.UI/wwwroot/appsettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Error", - "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information", - "System.Net.Http.HttpClient": "Error", - "Microsoft.AspNetCore.SignalR": "Debug", - "Microsoft.AspNetCore.Http.Connections": "Debug" - } - }, - "ShogiApiUrl": "https://localhost:5001", - "ShogiApiUrl2": "https://api.lucaserver.space/Shogi.Api/" -} \ No newline at end of file diff --git a/Shogi.UI/wwwroot/index.html b/Shogi.UI/wwwroot/index.html deleted file mode 100644 index 65953da..0000000 --- a/Shogi.UI/wwwroot/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - Shogi.UI - - - - - - - - -
- -
- An unhandled error has occurred. - Reload - 🗙 -
- - - - - diff --git a/Shogi.sln b/Shogi.sln index 349696f..d1948fd 100644 --- a/Shogi.sln +++ b/Shogi.sln @@ -1,18 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11312.210 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A968C8E6-47B7-4F72-A27A-AC9B643FD320}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.AcceptanceTests", "Tests\AcceptanceTests\Shogi.AcceptanceTests.csproj", "{30F4E3DB-027F-4885-BE06-884167C1C6CF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Contracts", "Shogi.Contracts\Shogi.Contracts.csproj", "{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E69DE334-29A7-46AE-9647-54DC0187CD8D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -22,65 +12,52 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.UI", "Shogi.UI\Shogi.UI.csproj", "{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}" -EndProject Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "Shogi.Database", "Shogi.Database\Shogi.Database.sqlproj", "{9B115B71-088F-41EF-858F-C7B155271A9F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Api", "Shogi.Api\Shogi.Api.csproj", "{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "Tests\E2ETests\E2ETests.csproj", "{401120C3-45D6-4A23-8D87-C2BED29F4950}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoardRules", "BoardRules\BoardRules.csproj", "{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{20DA20BB-85F1-4DBE-9B22-3C4FAF89647B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi.AcceptanceTests", "Tests\AcceptanceTests\Shogi.AcceptanceTests.csproj", "{768F37ED-FB62-A57F-BCFA-91F26B4F794F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{9D1DD2CD-7B04-4472-4377-027563F356CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi", "Shogi\Shogi.csproj", "{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.Build.0 = Release|Any CPU - {4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30F4E3DB-027F-4885-BE06-884167C1C6CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30F4E3DB-027F-4885-BE06-884167C1C6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30F4E3DB-027F-4885-BE06-884167C1C6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Release|Any CPU.Build.0 = Release|Any CPU - {12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Release|Any CPU.Build.0 = Release|Any CPU {9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.Deploy.0 = Release|Any CPU - {62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Release|Any CPU.Build.0 = Release|Any CPU - {401120C3-45D6-4A23-8D87-C2BED29F4950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {401120C3-45D6-4A23-8D87-C2BED29F4950}.Debug|Any CPU.Build.0 = Debug|Any CPU - {401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.ActiveCfg = Release|Any CPU - {401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.Build.0 = Release|Any CPU {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.Build.0 = Release|Any CPU + {768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Release|Any CPU.Build.0 = Release|Any CPU + {9D1DD2CD-7B04-4472-4377-027563F356CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D1DD2CD-7B04-4472-4377-027563F356CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D1DD2CD-7B04-4472-4377-027563F356CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D1DD2CD-7B04-4472-4377-027563F356CA}.Release|Any CPU.Build.0 = Release|Any CPU + {E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320} - {30F4E3DB-027F-4885-BE06-884167C1C6CF} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320} - {401120C3-45D6-4A23-8D87-C2BED29F4950} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320} + {768F37ED-FB62-A57F-BCFA-91F26B4F794F} = {20DA20BB-85F1-4DBE-9B22-3C4FAF89647B} + {9D1DD2CD-7B04-4472-4377-027563F356CA} = {20DA20BB-85F1-4DBE-9B22-3C4FAF89647B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB} diff --git a/Shogi.Api/.config/dotnet-tools.json b/Shogi/.config/dotnet-tools.json similarity index 100% rename from Shogi.Api/.config/dotnet-tools.json rename to Shogi/.config/dotnet-tools.json diff --git a/Shogi.Api/ApiKeys.cs b/Shogi/ApiKeys.cs similarity index 78% rename from Shogi.Api/ApiKeys.cs rename to Shogi/ApiKeys.cs index f19377d..3c7891e 100644 --- a/Shogi.Api/ApiKeys.cs +++ b/Shogi/ApiKeys.cs @@ -1,4 +1,4 @@ -namespace Shogi.Api; +namespace Shogi; public class ApiKeys { diff --git a/Shogi.Api/Application/GameHub.cs b/Shogi/BackEnd/Application/GameHub.cs similarity index 91% rename from Shogi.Api/Application/GameHub.cs rename to Shogi/BackEnd/Application/GameHub.cs index 4eb4b2d..f03e7fc 100644 --- a/Shogi.Api/Application/GameHub.cs +++ b/Shogi/BackEnd/Application/GameHub.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.SignalR; -namespace Shogi.Api.Application; +namespace Shogi.BackEnd.Application; /// /// Used to receive signals from connected clients. diff --git a/Shogi.Api/Application/GameHubContext.cs b/Shogi/BackEnd/Application/GameHubContext.cs similarity index 92% rename from Shogi.Api/Application/GameHubContext.cs rename to Shogi/BackEnd/Application/GameHubContext.cs index cbcead0..218d416 100644 --- a/Shogi.Api/Application/GameHubContext.cs +++ b/Shogi/BackEnd/Application/GameHubContext.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.SignalR; -namespace Shogi.Api.Application; +namespace Shogi.BackEnd.Application; /// /// Used to send signals to connected clients. diff --git a/Shogi.Api/Application/ShogiApplication.cs b/Shogi/BackEnd/Application/ShogiApplication.cs similarity index 89% rename from Shogi.Api/Application/ShogiApplication.cs rename to Shogi/BackEnd/Application/ShogiApplication.cs index 792e18a..5aa1f61 100644 --- a/Shogi.Api/Application/ShogiApplication.cs +++ b/Shogi/BackEnd/Application/ShogiApplication.cs @@ -1,15 +1,14 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Shogi.Api.Controllers; -using Shogi.Api.Extensions; -using Shogi.Api.Identity; -using Shogi.Api.Repositories; -using Shogi.Api.Repositories.Dto; -using Shogi.Contracts.Api.Commands; -using Shogi.Domain.Aggregates; +using Shogi.BackEnd.Controllers; +using Shogi.BackEnd.Extensions; +using Shogi.BackEnd.Identity; +using Shogi.BackEnd.Repositories; +using Shogi.BackEnd.Repositories.Dto; +using Shogi.BackEnd.Domains.Aggregates; using System.Data.SqlClient; -namespace Shogi.Api.Application; +namespace Shogi.BackEnd.Application; public class ShogiApplication( QueryRepository queryRepository, @@ -72,10 +71,10 @@ public class ShogiApplication( return session; } - public async Task MovePiece(string playerId, string sessionId, MovePieceCommand command) + public async Task MovePiece(string playerId, string sessionId, Types.MovePieceCommand command) { var session = await this.ReadSession(sessionId); - if (session == null) + if (session is null) { return new NotFoundResult(); } @@ -105,7 +104,7 @@ public class ShogiApplication( public async Task JoinSession(string sessionId, string player2Id) { var session = await this.ReadSession(sessionId); - if (session == null) return new NotFoundResult(); + if (session is null) return new NotFoundResult(); if (string.IsNullOrEmpty(session.Player2)) { diff --git a/Shogi.Api/Controllers/AccountController.cs b/Shogi/BackEnd/Controllers/AccountController.cs similarity index 96% rename from Shogi.Api/Controllers/AccountController.cs rename to Shogi/BackEnd/Controllers/AccountController.cs index a744f09..dda50b7 100644 --- a/Shogi.Api/Controllers/AccountController.cs +++ b/Shogi/BackEnd/Controllers/AccountController.cs @@ -1,10 +1,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Shogi.Api.Identity; +using Shogi.BackEnd.Identity; using System.Security.Claims; -namespace Shogi.Api.Controllers; +namespace Shogi.BackEnd.Controllers; [Authorize] [Route("[controller]")] diff --git a/Shogi.Api/Controllers/Extentions.cs b/Shogi/BackEnd/Controllers/Extentions.cs similarity index 85% rename from Shogi.Api/Controllers/Extentions.cs rename to Shogi/BackEnd/Controllers/Extentions.cs index eb828cb..4d6668e 100644 --- a/Shogi.Api/Controllers/Extentions.cs +++ b/Shogi/BackEnd/Controllers/Extentions.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace Shogi.Api.Controllers; +namespace Shogi.BackEnd.Controllers; public static class Extentions { diff --git a/Shogi.Api/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs b/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs similarity index 99% rename from Shogi.Api/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs rename to Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs index 85fe6e8..d64764f 100644 --- a/Shogi.Api/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs +++ b/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs @@ -15,7 +15,7 @@ using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; -namespace Shogi.Api.Controllers; +namespace Shogi.BackEnd.Controllers; /// /// Provides extension methods for to add identity endpoints. @@ -407,7 +407,7 @@ public static class MyIdentityApiEndpointRouteBuilderExtensions routeValues.Add("changedEmail", email); } - HostString? host = environment.IsDevelopment() ? null : new HostString("api.lucaserver.space/Shogi.Api"); + HostString? host = environment.IsDevelopment() ? null : new HostString("api.lucaserver.space/Shogi"); var confirmEmailUrl = linkGenerator.GetUriByName(context, confirmEmailEndpointName, routeValues, host: host) ?? throw new NotSupportedException($"Could not find endpoint named '{confirmEmailEndpointName}'."); diff --git a/Shogi.Api/Controllers/SessionsController.cs b/Shogi/BackEnd/Controllers/SessionsController.cs similarity index 70% rename from Shogi.Api/Controllers/SessionsController.cs rename to Shogi/BackEnd/Controllers/SessionsController.cs index 95b3a2b..f6695f6 100644 --- a/Shogi.Api/Controllers/SessionsController.cs +++ b/Shogi/BackEnd/Controllers/SessionsController.cs @@ -1,12 +1,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Shogi.Api.Application; -using Shogi.Api.Extensions; -using Shogi.Api.Repositories; -using Shogi.Contracts.Api.Commands; -using Shogi.Contracts.Types; +using Shogi.BackEnd.Application; +using Shogi.BackEnd.Extensions; +using Shogi.BackEnd.Repositories; +using Shogi.BackEnd.Types; -namespace Shogi.Api.Controllers; +namespace Shogi.BackEnd.Controllers; [Authorize] [ApiController] @@ -57,25 +56,25 @@ public class SessionsController( [AllowAnonymous] public async Task> GetSession(Guid sessionId) { - var session = await application.ReadSession(sessionId.ToString()); - if (session == null) return this.NotFound(); + var domainSession = await application.ReadSession(sessionId.ToString()); + if (domainSession is null) return this.NotFound(); return new Session { BoardState = new BoardState { - Board = session.Board.BoardState.State.ToContract(), - Player1Hand = session.Board.BoardState.Player1Hand.ToContract(), - Player2Hand = session.Board.BoardState.Player2Hand.ToContract(), - PlayerInCheck = session.Board.BoardState.InCheck?.ToContract(), - WhoseTurn = session.Board.BoardState.WhoseTurn.ToContract(), - Victor = session.Board.BoardState.IsCheckmate - ? session.Board.BoardState.InCheck == Domain.ValueObjects.WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1 + Board = domainSession.Board.BoardState.State.ToContract(), + Player1Hand = domainSession.Board.BoardState.Player1Hand.ToContract(), + Player2Hand = domainSession.Board.BoardState.Player2Hand.ToContract(), + PlayerInCheck = domainSession.Board.BoardState.InCheck?.ToContract(), + WhoseTurn = domainSession.Board.BoardState.WhoseTurn.ToContract(), + Victor = domainSession.Board.BoardState.IsCheckmate + ? domainSession.Board.BoardState.InCheck == Domains.ValueObjects.WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1 : null }, - Player1 = application.GetUsername(session.Player1), - Player2 = application.GetUsername(session.Player2), - SessionId = session.Id + Player1 = application.GetUsername(domainSession.Player1), + Player2 = application.GetUsername(domainSession.Player2), + SessionId = domainSession.Id }; } diff --git a/Shogi.Domain/Aggregates/Session.cs b/Shogi/BackEnd/Domains/Aggregates/Session.cs similarity index 86% rename from Shogi.Domain/Aggregates/Session.cs rename to Shogi/BackEnd/Domains/Aggregates/Session.cs index 4c76fcf..4cc1cb7 100644 --- a/Shogi.Domain/Aggregates/Session.cs +++ b/Shogi/BackEnd/Domains/Aggregates/Session.cs @@ -1,6 +1,7 @@ -using Shogi.Domain.ValueObjects; +using Shogi.BackEnd.Domains.ValueObjects; +using Shogi.BackEnd.Domains.ValueObjects.Rules; -namespace Shogi.Domain.Aggregates; +namespace Shogi.BackEnd.Domains.Aggregates; public class Session(Guid id, string player1Name) { diff --git a/Shogi.Domain/ValueObjects/BoardState.cs b/Shogi/BackEnd/Domains/ValueObjects/BoardState.cs similarity index 96% rename from Shogi.Domain/ValueObjects/BoardState.cs rename to Shogi/BackEnd/Domains/ValueObjects/BoardState.cs index 03a51a6..ff0b982 100644 --- a/Shogi.Domain/ValueObjects/BoardState.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/BoardState.cs @@ -1,8 +1,11 @@ -using System.Collections.ObjectModel; -using Shogi.Domain.YetToBeAssimilatedIntoDDD; -using BoardTile = System.Collections.Generic.KeyValuePair; +using System.Collections.ObjectModel; +using System.Numerics; +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using Shogi.BackEnd.Domains.ValueObjects.Pieces; +using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD; +using BoardTile = System.Collections.Generic.KeyValuePair; -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects; public class BoardState { diff --git a/Shogi.Domain/ValueObjects/Enums.cs b/Shogi/BackEnd/Domains/ValueObjects/Enums.cs similarity index 91% rename from Shogi.Domain/ValueObjects/Enums.cs rename to Shogi/BackEnd/Domains/ValueObjects/Enums.cs index e259d0c..a7781d0 100644 --- a/Shogi.Domain/ValueObjects/Enums.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Enums.cs @@ -1,4 +1,4 @@ -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects; [Flags] internal enum InCheckResult diff --git a/Shogi/BackEnd/Domains/ValueObjects/Movement/Direction.cs b/Shogi/BackEnd/Domains/ValueObjects/Movement/Direction.cs new file mode 100644 index 0000000..f1a3427 --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Movement/Direction.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace Shogi.BackEnd.Domains.ValueObjects.Movement; + +public static class Direction +{ + public static readonly Vector2 Forward = new(0, 1); + public static readonly Vector2 Backward = new(0, -1); + public static readonly Vector2 Left = new(-1, 0); + public static readonly Vector2 Right = new(1, 0); + public static readonly Vector2 ForwardLeft = new(-1, 1); + public static readonly Vector2 ForwardRight = new(1, 1); + public static readonly Vector2 BackwardLeft = new(-1, -1); + public static readonly Vector2 BackwardRight = new(1, -1); + public static readonly Vector2 KnightLeft = new(-1, 2); + public static readonly Vector2 KnightRight = new(1, 2); +} diff --git a/Shogi.Domain/ValueObjects/Movement/Distance.cs b/Shogi/BackEnd/Domains/ValueObjects/Movement/Distance.cs similarity index 81% rename from Shogi.Domain/ValueObjects/Movement/Distance.cs rename to Shogi/BackEnd/Domains/ValueObjects/Movement/Distance.cs index dfa7362..1ad6fdf 100644 --- a/Shogi.Domain/ValueObjects/Movement/Distance.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Movement/Distance.cs @@ -1,4 +1,4 @@ -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects.Movement; public enum Distance { @@ -10,4 +10,4 @@ public enum Distance /// Signifies that a piece can move multiple tiles/positions in a single move. /// MultiStep -} \ No newline at end of file +} diff --git a/Shogi.Domain/ValueObjects/Movement/Move.cs b/Shogi/BackEnd/Domains/ValueObjects/Movement/Move.cs similarity index 86% rename from Shogi.Domain/ValueObjects/Movement/Move.cs rename to Shogi/BackEnd/Domains/ValueObjects/Movement/Move.cs index a9d7e45..b5cd595 100644 --- a/Shogi.Domain/ValueObjects/Movement/Move.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Movement/Move.cs @@ -1,4 +1,6 @@ -namespace Shogi.Domain.ValueObjects; +using System.Numerics; + +namespace Shogi.BackEnd.Domains.ValueObjects.Movement; /// /// Represents a single piece being moved by a player from to . diff --git a/Shogi/BackEnd/Domains/ValueObjects/Movement/MoveResult.cs b/Shogi/BackEnd/Domains/ValueObjects/Movement/MoveResult.cs new file mode 100644 index 0000000..06362dc --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Movement/MoveResult.cs @@ -0,0 +1,5 @@ +namespace Shogi.BackEnd.Domains.ValueObjects.Movement; + +public record MoveResult(bool IsSuccess, string Reason = "") +{ +} diff --git a/Shogi.Domain/ValueObjects/Movement/Path.cs b/Shogi/BackEnd/Domains/ValueObjects/Movement/Path.cs similarity index 83% rename from Shogi.Domain/ValueObjects/Movement/Path.cs rename to Shogi/BackEnd/Domains/ValueObjects/Movement/Path.cs index 14ee1a5..65868e7 100644 --- a/Shogi.Domain/ValueObjects/Movement/Path.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Movement/Path.cs @@ -1,6 +1,7 @@ -using System.Diagnostics; +using System.Diagnostics; +using System.Numerics; -namespace Shogi.Domain.ValueObjects.Movement; +namespace Shogi.BackEnd.Domains.ValueObjects.Movement; [DebuggerDisplay("{Step} - {Distance}")] public record Path @@ -21,4 +22,4 @@ public record Path } public Path Invert() => new(Vector2.Negate(Step), Distance); -} \ No newline at end of file +} diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/Bishop.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Bishop.cs new file mode 100644 index 0000000..3751e4d --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Bishop.cs @@ -0,0 +1,47 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +internal record class Bishop : Piece +{ + private static readonly ReadOnlyCollection BishopPaths = new(new List(4) + { + new Path(Direction.ForwardLeft, Distance.MultiStep), + new Path(Direction.ForwardRight, Distance.MultiStep), + new Path(Direction.BackwardLeft, Distance.MultiStep), + new Path(Direction.BackwardRight, Distance.MultiStep) + }); + + public static readonly ReadOnlyCollection PromotedBishopPaths = new(new List(8) + { + new Path(Direction.Forward), + new Path(Direction.Left), + new Path(Direction.Right), + new Path(Direction.Backward), + new Path(Direction.ForwardLeft, Distance.MultiStep), + new Path(Direction.ForwardRight, Distance.MultiStep), + new Path(Direction.BackwardLeft, Distance.MultiStep), + new Path(Direction.BackwardRight, Distance.MultiStep) + }); + + public static readonly ReadOnlyCollection Player2Paths = + BishopPaths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public static readonly ReadOnlyCollection Player2PromotedPaths = + PromotedBishopPaths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public Bishop(WhichPlayer owner, bool isPromoted = false) + : base(WhichPiece.Bishop, owner, isPromoted) + { + } + + public override IEnumerable MoveSet => IsPromoted ? PromotedBishopPaths : BishopPaths; +} diff --git a/Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/GoldGeneral.cs similarity index 81% rename from Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs rename to Shogi/BackEnd/Domains/ValueObjects/Pieces/GoldGeneral.cs index a45a0b2..c6f35fe 100644 --- a/Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/GoldGeneral.cs @@ -1,7 +1,8 @@ -using Shogi.Domain.ValueObjects.Movement; +using Shogi.BackEnd.Domains.ValueObjects.Movement; using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; internal record class GoldGeneral : Piece { diff --git a/Shogi.Domain/ValueObjects/Pieces/King.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/King.cs similarity index 82% rename from Shogi.Domain/ValueObjects/Pieces/King.cs rename to Shogi/BackEnd/Domains/ValueObjects/Pieces/King.cs index 6f66cf5..e0e40c2 100644 --- a/Shogi.Domain/ValueObjects/Pieces/King.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/King.cs @@ -1,7 +1,8 @@ -using Shogi.Domain.ValueObjects.Movement; +using Shogi.BackEnd.Domains.ValueObjects.Movement; using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; internal record class King : Piece { diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/Knight.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Knight.cs new file mode 100644 index 0000000..f0c35a9 --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Knight.cs @@ -0,0 +1,32 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +internal record class Knight : Piece +{ + public static readonly ReadOnlyCollection Player1Paths = new(new List(2) + { + new Path(Direction.KnightLeft), + new Path(Direction.KnightRight) + }); + + public static readonly ReadOnlyCollection Player2Paths = + Player1Paths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public Knight(WhichPlayer owner, bool isPromoted = false) + : base(WhichPiece.Knight, owner, isPromoted) + { + } + + public override ReadOnlyCollection MoveSet => Owner switch + { + WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, + WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, + _ => throw new NotImplementedException(), + }; +} diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/Lance.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Lance.cs new file mode 100644 index 0000000..3d1be3d --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Lance.cs @@ -0,0 +1,31 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +internal record class Lance : Piece +{ + public static readonly ReadOnlyCollection Player1Paths = new(new List(1) + { + new Path(Direction.Forward, Distance.MultiStep), + }); + + public static readonly ReadOnlyCollection Player2Paths = + Player1Paths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public Lance(WhichPlayer owner, bool isPromoted = false) + : base(WhichPiece.Lance, owner, isPromoted) + { + } + + public override ReadOnlyCollection MoveSet => Owner switch + { + WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, + WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, + _ => throw new NotImplementedException(), + }; +} diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/Pawn.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Pawn.cs new file mode 100644 index 0000000..5f9de3a --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Pawn.cs @@ -0,0 +1,31 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +internal record class Pawn : Piece +{ + public static readonly ReadOnlyCollection Player1Paths = new(new List(1) + { + new Path(Direction.Forward) + }); + + public static readonly ReadOnlyCollection Player2Paths = + Player1Paths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public Pawn(WhichPlayer owner, bool isPromoted = false) + : base(WhichPiece.Pawn, owner, isPromoted) + { + } + + public override ReadOnlyCollection MoveSet => Owner switch + { + WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, + WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, + _ => throw new NotImplementedException(), + }; +} diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/Piece.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Piece.cs new file mode 100644 index 0000000..c18daf3 --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Piece.cs @@ -0,0 +1,100 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Diagnostics; +using System.Numerics; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +[DebuggerDisplay("{WhichPiece} {Owner}")] +public abstract record class Piece +{ + public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) + { + return piece switch + { + WhichPiece.King => new King(owner, isPromoted), + WhichPiece.GoldGeneral => new GoldGeneral(owner, isPromoted), + WhichPiece.SilverGeneral => new SilverGeneral(owner, isPromoted), + WhichPiece.Bishop => new Bishop(owner, isPromoted), + WhichPiece.Rook => new Rook(owner, isPromoted), + WhichPiece.Knight => new Knight(owner, isPromoted), + WhichPiece.Lance => new Lance(owner, isPromoted), + WhichPiece.Pawn => new Pawn(owner, isPromoted), + _ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.") + }; + } + public abstract IEnumerable MoveSet { get; } + public WhichPiece WhichPiece { get; } + public WhichPlayer Owner { get; private set; } + public bool IsPromoted { get; private set; } + protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) + { + WhichPiece = piece; + Owner = owner; + IsPromoted = isPromoted; + } + + public bool CanPromote => !IsPromoted + && WhichPiece != WhichPiece.King + && WhichPiece != WhichPiece.GoldGeneral; + + public void Promote() => IsPromoted = CanPromote; + + /// + /// Prep the piece for capture by changing ownership and demoting. + /// + public void Capture(WhichPlayer newOwner) + { + Owner = newOwner; + IsPromoted = false; + } + + /// + /// Respecting the move-set of the Piece, collect all positions along the shortest path from start to end. + /// Useful if you need to iterate a move-set. + /// + /// + /// + /// An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions. + public IEnumerable GetPathFromStartToEnd(Vector2 start, Vector2 end) + { + var steps = new List(10); + + var path = GetNearestPath(MoveSet, start, end); + var position = start; + while (Vector2.Distance(start, position) < Vector2.Distance(start, end)) + { + position += path.Step; + steps.Add(position); + + if (path.Distance == Distance.OneStep) break; + } + + if (position == end) + { + return steps; + } + + return []; + } + + private static Path GetNearestPath(IEnumerable paths, Vector2 start, Vector2 end) + { + if (!paths.DefaultIfEmpty().Any()) + { + throw new ArgumentException("No paths to get nearest path from."); + } + + var shortestPath = paths.First(); + foreach (var path in paths.Skip(1)) + { + var distance = Vector2.Distance(start + path.Step, end); + var shortestDistance = Vector2.Distance(start + shortestPath.Step, end); + if (distance < shortestDistance) + { + shortestPath = path; + } + } + return shortestPath; + } +} diff --git a/Shogi.Domain/ValueObjects/Pieces/Rook.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Rook.cs similarity index 90% rename from Shogi.Domain/ValueObjects/Pieces/Rook.cs rename to Shogi/BackEnd/Domains/ValueObjects/Pieces/Rook.cs index 5624600..5a65f12 100644 --- a/Shogi.Domain/ValueObjects/Pieces/Rook.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/Rook.cs @@ -1,7 +1,8 @@ -using Shogi.Domain.ValueObjects.Movement; +using Shogi.BackEnd.Domains.ValueObjects.Movement; using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; -namespace Shogi.Domain.ValueObjects; +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; public record class Rook : Piece { diff --git a/Shogi/BackEnd/Domains/ValueObjects/Pieces/SilverGeneral.cs b/Shogi/BackEnd/Domains/ValueObjects/Pieces/SilverGeneral.cs new file mode 100644 index 0000000..9d23a03 --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Pieces/SilverGeneral.cs @@ -0,0 +1,35 @@ +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using System.Collections.ObjectModel; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Pieces; + +internal record class SilverGeneral : Piece +{ + public static readonly ReadOnlyCollection Player1Paths = new(new List(4) + { + new Path(Direction.Forward), + new Path(Direction.ForwardLeft), + new Path(Direction.ForwardRight), + new Path(Direction.BackwardLeft), + new Path(Direction.BackwardRight) + }); + + public static readonly ReadOnlyCollection Player2Paths = + Player1Paths + .Select(p => p.Invert()) + .ToList() + .AsReadOnly(); + + public SilverGeneral(WhichPlayer owner, bool isPromoted = false) + : base(WhichPiece.SilverGeneral, owner, isPromoted) + { + } + + public override ReadOnlyCollection MoveSet => Owner switch + { + WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths, + WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths, + _ => throw new NotImplementedException(), + }; +} diff --git a/Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs b/Shogi/BackEnd/Domains/ValueObjects/Rules/ShogiBoard.cs similarity index 97% rename from Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs rename to Shogi/BackEnd/Domains/ValueObjects/Rules/ShogiBoard.cs index b139566..0d6e944 100644 --- a/Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs +++ b/Shogi/BackEnd/Domains/ValueObjects/Rules/ShogiBoard.cs @@ -1,6 +1,10 @@ -using Shogi.Domain.ValueObjects.Movement; -using Shogi.Domain.YetToBeAssimilatedIntoDDD; -namespace Shogi.Domain.ValueObjects; +using System.Numerics; +using Shogi.BackEnd.Domains.ValueObjects.Movement; +using Shogi.BackEnd.Domains.ValueObjects.Pieces; +using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD; +using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path; + +namespace Shogi.BackEnd.Domains.ValueObjects.Rules; /// /// Facilitates Shogi board state transitions, cognisant of Shogi rules. diff --git a/Shogi/BackEnd/Domains/ValueObjects/Rules/StandardRules.cs b/Shogi/BackEnd/Domains/ValueObjects/Rules/StandardRules.cs new file mode 100644 index 0000000..fc47ceb --- /dev/null +++ b/Shogi/BackEnd/Domains/ValueObjects/Rules/StandardRules.cs @@ -0,0 +1,166 @@ +using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD; +using System.Numerics; +using BoardTile = System.Collections.Generic.KeyValuePair; + +namespace Shogi.BackEnd.Domains.ValueObjects.Rules; + +internal class StandardRules +{ + private readonly BoardState boardState; + + internal StandardRules(BoardState board) + { + boardState = board; + } + + /// + /// Determines if the last move put the player who moved in check. + /// + /// + /// This strategy recognizes that a "discover check" could only occur from a subset of pieces: Rook, Bishop, Lance. + /// In this way, only those pieces need to be considered when evaluating if a move placed the moving player in check. + /// + internal bool DidPlayerPutThemselfInCheck() + { + if (boardState.PreviousMove.From == null) + { + // You can't place yourself in check by placing a piece from your hand. + return false; + } + + var previousMovedPiece = boardState[boardState.PreviousMove.To]; + if (previousMovedPiece == null) throw new ArgumentNullException(nameof(previousMovedPiece), $"No piece exists at position {boardState.PreviousMove.To}."); + var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition; + + + var isDiscoverCheck = false; + // Get line equation from king through the now-unoccupied location. + var direction = Vector2.Subtract(kingPosition, boardState.PreviousMove.From.Value); + var slope = Math.Abs(direction.Y / direction.X); + var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMove.From.Value, Vector2.Normalize(direction)); + var threat = boardState.QueryFirstPieceInPath(path); + if (threat == null || threat.Owner == previousMovedPiece.Owner) return false; + // 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)) + { + isDiscoverCheck = threat.WhichPiece switch + { + WhichPiece.Lance => !threat.IsPromoted, + WhichPiece.Rook => true, + _ => false + }; + } + else if (slope == 1) + { + isDiscoverCheck = threat.WhichPiece switch + { + WhichPiece.Bishop => true, + _ => false + }; + } + else if (slope == 0) + { + isDiscoverCheck = threat.WhichPiece switch + { + WhichPiece.Rook => true, + _ => false + }; + } + return isDiscoverCheck; + } + + internal bool IsOpponentInCheckAfterMove() => IsOpposingKingThreatenedByPosition(boardState.PreviousMove.To); + + internal bool IsOpposingKingThreatenedByPosition(Vector2 position) + { + var previousMovedPiece = boardState[position]; + if (previousMovedPiece == null) return false; + + var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition; + var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition); + var threatenedPiece = boardState.QueryFirstPieceInPath(path); + if (!path.Any() || threatenedPiece == null) return false; + + return threatenedPiece.WhichPiece == WhichPiece.King; + } + + internal bool IsOpponentInCheckMate() + { + // Assume checkmate, then try to disprove. + if (!boardState.InCheck.HasValue) return false; + // Get all pieces from opponent who threaten the king in question. + var opponent = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; + var tilesOccupiedByOpponent = boardState.GetTilesOccupiedBy(opponent); + var kingPosition = boardState.WhoseTurn == WhichPlayer.Player1 + ? boardState.Player1KingPosition + : boardState.Player2KingPosition; + var threats = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList(); + if (threats.Count == 1) + { + /* If there is exactly one threat it is possible to block the check. + * Foreach piece owned by whichPlayer + * if piece can intercept check, return false; + */ + var threat = threats.Single(); + var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition); + var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn); + foreach (var threatBlockingPosition in pathFromThreatToKing) + { + var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat + .Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition)) + .ToList(); + + if (tilesThatDoBlockThreat.Any()) + { + return false; // Cannot be check-mate if a piece can intercept the threat. + } + } + } + else + { + /* + * If no ability to block the check, maybe the king can evade check by moving. + */ + + foreach (var maybeSafePosition in GetPossiblePositionsForKing(boardState.WhoseTurn)) + { + threats = tilesOccupiedByOpponent + .Where(tile => PieceHasLineOfSight(tile, maybeSafePosition)) + .ToList(); + if (!threats.Any()) + { + return false; + } + } + + } + + return true; + } + + private List GetPossiblePositionsForKing(WhichPlayer whichPlayer) + { + var kingPosition = whichPlayer == WhichPlayer.Player1 + ? boardState.Player1KingPosition + : boardState.Player2KingPosition; + + var paths = boardState[kingPosition]!.MoveSet; + return paths + .Select(path => path.Step + kingPosition) + // Because the king could be on the edge of the board, where some of its paths do not make sense. + .Where(newPosition => newPosition.IsInsideBoardBoundary()) + // Where tile at position is empty, meaning the king could move there. + .Where(newPosition => boardState[newPosition] == null) + .ToList(); + } + + private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget) + { + var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget); + return path + .SkipLast(1) + .All(position => boardState[Notation.ToBoardNotation(position)] == null); + } +} diff --git a/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/DomainExtensions.cs b/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/DomainExtensions.cs new file mode 100644 index 0000000..5860191 --- /dev/null +++ b/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/DomainExtensions.cs @@ -0,0 +1,19 @@ +using Shogi.BackEnd.Domains.ValueObjects; +using Shogi.BackEnd.Domains.ValueObjects.Pieces; + +namespace Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD; + +internal static class DomainExtensions +{ + public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King; + + public static bool IsBetween(this float self, float min, float max) + { + return self >= min && self <= max; + } + + public static bool IsInsideBoardBoundary(this System.Numerics.Vector2 self) + { + return self.X.IsBetween(0, 8) && self.Y.IsBetween(0, 8); + } +} diff --git a/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/Notation.cs b/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/Notation.cs new file mode 100644 index 0000000..3fee9cd --- /dev/null +++ b/Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/Notation.cs @@ -0,0 +1,33 @@ +using System.Numerics; +using System.Text.RegularExpressions; + +namespace Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD; + +public static class Notation +{ + private static readonly string BoardNotationRegex = @"(?[A-I])(?[1-9])"; + private static readonly char A = 'A'; + + public static string ToBoardNotation(Vector2 vector) + { + return ToBoardNotation((int)vector.X, (int)vector.Y); + } + + public static string ToBoardNotation(int x, int y) + { + var file = (char)(x + A); + var rank = y + 1; + return $"{file}{rank}"; + } + public static Vector2 FromBoardNotation(string notation) + { + if (Regex.IsMatch(notation, BoardNotationRegex)) + { + var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase); + char file = match.Groups["file"].Value[0]; + int rank = int.Parse(match.Groups["rank"].Value); + return new Vector2(file - A, rank - 1); + } + throw new ArgumentException($"Board notation not recognized. Notation given: {notation}"); + } +} diff --git a/Shogi/BackEnd/Extensions/ContractsExtensions.cs b/Shogi/BackEnd/Extensions/ContractsExtensions.cs new file mode 100644 index 0000000..fffba31 --- /dev/null +++ b/Shogi/BackEnd/Extensions/ContractsExtensions.cs @@ -0,0 +1,68 @@ +using Shogi.BackEnd.Types; +using System.Collections.ObjectModel; +using DomainValueObjects = Shogi.BackEnd.Domains.ValueObjects; + +namespace Shogi.BackEnd.Extensions; + +public static class ContractsExtensions +{ + public static WhichPlayer ToContract(this DomainValueObjects.WhichPlayer player) + { + return player switch + { + DomainValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1, + DomainValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2, + _ => throw new NotImplementedException(), + }; + } + + public static WhichPiece ToContract(this DomainValueObjects.WhichPiece piece) + { + return piece switch + { + DomainValueObjects.WhichPiece.King => WhichPiece.King, + DomainValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral, + DomainValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral, + DomainValueObjects.WhichPiece.Bishop => WhichPiece.Bishop, + DomainValueObjects.WhichPiece.Rook => WhichPiece.Rook, + DomainValueObjects.WhichPiece.Knight => WhichPiece.Knight, + DomainValueObjects.WhichPiece.Lance => WhichPiece.Lance, + DomainValueObjects.WhichPiece.Pawn => WhichPiece.Pawn, + _ => throw new NotImplementedException(), + }; + } + + public static Piece ToContract(this DomainValueObjects.Pieces.Piece piece) => new() + { + IsPromoted = piece.IsPromoted, + Owner = piece.Owner.ToContract(), + WhichPiece = piece.WhichPiece.ToContract() + }; + + public static IReadOnlyList ToContract(this List pieces) + { + return pieces + .Select(ToContract) + .ToList() + .AsReadOnly(); + } + + public static Dictionary ToContract(this ReadOnlyDictionary boardState) => + boardState.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToContract()); + + public static DomainValueObjects.WhichPiece ToDomain(this WhichPiece piece) + { + return piece switch + { + WhichPiece.King => DomainValueObjects.WhichPiece.King, + WhichPiece.GoldGeneral => DomainValueObjects.WhichPiece.GoldGeneral, + WhichPiece.SilverGeneral => DomainValueObjects.WhichPiece.SilverGeneral, + WhichPiece.Bishop => DomainValueObjects.WhichPiece.Bishop, + WhichPiece.Rook => DomainValueObjects.WhichPiece.Rook, + WhichPiece.Knight => DomainValueObjects.WhichPiece.Knight, + WhichPiece.Lance => DomainValueObjects.WhichPiece.Lance, + WhichPiece.Pawn => DomainValueObjects.WhichPiece.Pawn, + _ => throw new NotImplementedException(), + }; + } +} diff --git a/Shogi.Api/Identity/ApplicationDbContext.cs b/Shogi/BackEnd/Identity/ApplicationDbContext.cs similarity index 86% rename from Shogi.Api/Identity/ApplicationDbContext.cs rename to Shogi/BackEnd/Identity/ApplicationDbContext.cs index 13be2bb..c5dcd86 100644 --- a/Shogi.Api/Identity/ApplicationDbContext.cs +++ b/Shogi/BackEnd/Identity/ApplicationDbContext.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace Shogi.Api.Identity; +namespace Shogi.BackEnd.Identity; public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) { diff --git a/Shogi.Api/Identity/ShogiUser.cs b/Shogi/BackEnd/Identity/ShogiUser.cs similarity index 71% rename from Shogi.Api/Identity/ShogiUser.cs rename to Shogi/BackEnd/Identity/ShogiUser.cs index 22eb31d..4bbe2da 100644 --- a/Shogi.Api/Identity/ShogiUser.cs +++ b/Shogi/BackEnd/Identity/ShogiUser.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; -namespace Shogi.Api.Identity; +namespace Shogi.BackEnd.Identity; public class ShogiUser : IdentityUser { diff --git a/Shogi.Api/Migrations/20240816002834_InitialCreate.Designer.cs b/Shogi/BackEnd/Migrations/20240816002834_InitialCreate.Designer.cs similarity index 96% rename from Shogi.Api/Migrations/20240816002834_InitialCreate.Designer.cs rename to Shogi/BackEnd/Migrations/20240816002834_InitialCreate.Designer.cs index 24c2203..cf87bba 100644 --- a/Shogi.Api/Migrations/20240816002834_InitialCreate.Designer.cs +++ b/Shogi/BackEnd/Migrations/20240816002834_InitialCreate.Designer.cs @@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Shogi.Api.Identity; +using Shogi.BackEnd.Identity; #nullable disable -namespace Shogi.Api.Migrations +namespace Shogi.BackEnd.Migrations { [DbContext(typeof(ApplicationDbContext))] [Migration("20240816002834_InitialCreate")] @@ -158,7 +158,7 @@ namespace Shogi.Api.Migrations b.ToTable("AspNetUserTokens", (string)null); }); - modelBuilder.Entity("Shogi.Api.Models.User", b => + modelBuilder.Entity("Shogi.Models.User", b => { b.Property("Id") .HasColumnType("nvarchar(450)"); @@ -234,7 +234,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -243,7 +243,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -258,7 +258,7 @@ namespace Shogi.Api.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -267,7 +267,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) diff --git a/Shogi.Api/Migrations/20240816002834_InitialCreate.cs b/Shogi/BackEnd/Migrations/20240816002834_InitialCreate.cs similarity index 99% rename from Shogi.Api/Migrations/20240816002834_InitialCreate.cs rename to Shogi/BackEnd/Migrations/20240816002834_InitialCreate.cs index 9ddf303..e405349 100644 --- a/Shogi.Api/Migrations/20240816002834_InitialCreate.cs +++ b/Shogi/BackEnd/Migrations/20240816002834_InitialCreate.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Shogi.Api.Migrations +namespace Shogi.BackEnd.Migrations { /// public partial class InitialCreate : Migration diff --git a/Shogi.Api/Migrations/ApplicationDbContextModelSnapshot.cs b/Shogi/BackEnd/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 96% rename from Shogi.Api/Migrations/ApplicationDbContextModelSnapshot.cs rename to Shogi/BackEnd/Migrations/ApplicationDbContextModelSnapshot.cs index bc2412f..8ae44b3 100644 --- a/Shogi.Api/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Shogi/BackEnd/Migrations/ApplicationDbContextModelSnapshot.cs @@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Shogi.Api.Identity; +using Shogi.BackEnd.Identity; #nullable disable -namespace Shogi.Api.Migrations +namespace Shogi.BackEnd.Migrations { [DbContext(typeof(ApplicationDbContext))] partial class ApplicationDbContextModelSnapshot : ModelSnapshot @@ -155,7 +155,7 @@ namespace Shogi.Api.Migrations b.ToTable("AspNetUserTokens", (string)null); }); - modelBuilder.Entity("Shogi.Api.Models.User", b => + modelBuilder.Entity("Shogi.Models.User", b => { b.Property("Id") .HasColumnType("nvarchar(450)"); @@ -231,7 +231,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -240,7 +240,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -255,7 +255,7 @@ namespace Shogi.Api.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -264,7 +264,7 @@ namespace Shogi.Api.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Shogi.Api.Models.User", null) + b.HasOne("Shogi.Models.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) diff --git a/Shogi.Api/Repositories/CouchModels/BoardStateDocument.cs b/Shogi/BackEnd/Repositories/CouchModels/BoardStateDocument.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/BoardStateDocument.cs rename to Shogi/BackEnd/Repositories/CouchModels/BoardStateDocument.cs diff --git a/Shogi.Api/Repositories/CouchModels/CouchCreatedResult.cs b/Shogi/BackEnd/Repositories/CouchModels/CouchCreatedResult.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/CouchCreatedResult.cs rename to Shogi/BackEnd/Repositories/CouchModels/CouchCreatedResult.cs diff --git a/Shogi.Api/Repositories/CouchModels/CouchDocument.cs b/Shogi/BackEnd/Repositories/CouchModels/CouchDocument.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/CouchDocument.cs rename to Shogi/BackEnd/Repositories/CouchModels/CouchDocument.cs diff --git a/Shogi.Api/Repositories/CouchModels/CouchFindResult.cs b/Shogi/BackEnd/Repositories/CouchModels/CouchFindResult.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/CouchFindResult.cs rename to Shogi/BackEnd/Repositories/CouchModels/CouchFindResult.cs diff --git a/Shogi.Api/Repositories/CouchModels/CouchViewResult.cs b/Shogi/BackEnd/Repositories/CouchModels/CouchViewResult.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/CouchViewResult.cs rename to Shogi/BackEnd/Repositories/CouchModels/CouchViewResult.cs diff --git a/Shogi.Api/Repositories/CouchModels/Move.cs b/Shogi/BackEnd/Repositories/CouchModels/Move.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/Move.cs rename to Shogi/BackEnd/Repositories/CouchModels/Move.cs diff --git a/Shogi.Api/Repositories/CouchModels/Piece.cs b/Shogi/BackEnd/Repositories/CouchModels/Piece.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/Piece.cs rename to Shogi/BackEnd/Repositories/CouchModels/Piece.cs diff --git a/Shogi.Api/Repositories/CouchModels/SessionDocument.cs b/Shogi/BackEnd/Repositories/CouchModels/SessionDocument.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/SessionDocument.cs rename to Shogi/BackEnd/Repositories/CouchModels/SessionDocument.cs diff --git a/Shogi.Api/Repositories/CouchModels/UserDocument.cs b/Shogi/BackEnd/Repositories/CouchModels/UserDocument.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/UserDocument.cs rename to Shogi/BackEnd/Repositories/CouchModels/UserDocument.cs diff --git a/Shogi.Api/Repositories/CouchModels/WhichDocumentType.cs b/Shogi/BackEnd/Repositories/CouchModels/WhichDocumentType.cs similarity index 100% rename from Shogi.Api/Repositories/CouchModels/WhichDocumentType.cs rename to Shogi/BackEnd/Repositories/CouchModels/WhichDocumentType.cs diff --git a/Shogi.Api/Repositories/Dto/MoveDto.cs b/Shogi/BackEnd/Repositories/Dto/MoveDto.cs similarity index 68% rename from Shogi.Api/Repositories/Dto/MoveDto.cs rename to Shogi/BackEnd/Repositories/Dto/MoveDto.cs index 2c6b086..02809e5 100644 --- a/Shogi.Api/Repositories/Dto/MoveDto.cs +++ b/Shogi/BackEnd/Repositories/Dto/MoveDto.cs @@ -1,6 +1,6 @@ -using Shogi.Domain.ValueObjects; +using Shogi.BackEnd.Domains.ValueObjects; -namespace Shogi.Api.Repositories.Dto; +namespace Shogi.BackEnd.Repositories.Dto; /// /// Useful with Dapper to read from database. diff --git a/Shogi.Api/Repositories/Dto/SessionDto.cs b/Shogi/BackEnd/Repositories/Dto/SessionDto.cs similarity index 67% rename from Shogi.Api/Repositories/Dto/SessionDto.cs rename to Shogi/BackEnd/Repositories/Dto/SessionDto.cs index bec4145..406e7d2 100644 --- a/Shogi.Api/Repositories/Dto/SessionDto.cs +++ b/Shogi/BackEnd/Repositories/Dto/SessionDto.cs @@ -1,4 +1,4 @@ -namespace Shogi.Api.Repositories.Dto; +namespace Shogi.BackEnd.Repositories.Dto; public readonly record struct SessionDto(string Id, string Player1Id, string Player2Id) { diff --git a/Shogi.Api/Repositories/EmailSender.cs b/Shogi/BackEnd/Repositories/EmailSender.cs similarity index 97% rename from Shogi.Api/Repositories/EmailSender.cs rename to Shogi/BackEnd/Repositories/EmailSender.cs index 84c1e69..4e14364 100644 --- a/Shogi.Api/Repositories/EmailSender.cs +++ b/Shogi/BackEnd/Repositories/EmailSender.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Options; using System.Net.Http.Headers; using System.Text.Json; -namespace Shogi.Api.Repositories; +namespace Shogi.BackEnd.Repositories; // https://app-smtp.brevo.com/real-time diff --git a/Shogi.Api/Repositories/GameboardRepository.cs b/Shogi/BackEnd/Repositories/GameboardRepository.cs similarity index 100% rename from Shogi.Api/Repositories/GameboardRepository.cs rename to Shogi/BackEnd/Repositories/GameboardRepository.cs diff --git a/Shogi.Api/Repositories/QueryRepository.cs b/Shogi/BackEnd/Repositories/QueryRepository.cs similarity index 90% rename from Shogi.Api/Repositories/QueryRepository.cs rename to Shogi/BackEnd/Repositories/QueryRepository.cs index 6b489fb..c8a163f 100644 --- a/Shogi.Api/Repositories/QueryRepository.cs +++ b/Shogi/BackEnd/Repositories/QueryRepository.cs @@ -1,9 +1,9 @@ using Dapper; -using Shogi.Api.Repositories.Dto; +using Shogi.BackEnd.Repositories.Dto; using System.Data; using System.Data.SqlClient; -namespace Shogi.Api.Repositories; +namespace Shogi.BackEnd.Repositories; public class QueryRepository(IConfiguration configuration) { diff --git a/Shogi.Api/Repositories/SessionRepository.cs b/Shogi/BackEnd/Repositories/SessionRepository.cs similarity index 90% rename from Shogi.Api/Repositories/SessionRepository.cs rename to Shogi/BackEnd/Repositories/SessionRepository.cs index 7ccc6e5..65c8d11 100644 --- a/Shogi.Api/Repositories/SessionRepository.cs +++ b/Shogi/BackEnd/Repositories/SessionRepository.cs @@ -1,11 +1,10 @@ using Dapper; -using Shogi.Api.Repositories.Dto; -using Shogi.Contracts.Api.Commands; -using Shogi.Domain.Aggregates; +using Shogi.BackEnd.Repositories.Dto; +using Shogi.BackEnd.Domains.Aggregates; using System.Data; using System.Data.SqlClient; -namespace Shogi.Api.Repositories; +namespace Shogi.BackEnd.Repositories; public class SessionRepository(IConfiguration configuration) { @@ -53,7 +52,7 @@ public class SessionRepository(IConfiguration configuration) return new(sessionDtos.First(), moveDtos); } - public async Task CreateMove(string sessionId, MovePieceCommand command) + public async Task CreateMove(string sessionId, Types.MovePieceCommand command) { using var connection = new SqlConnection(this.connectionString); await connection.ExecuteAsync( diff --git a/Shogi.Contracts/Types/BoardState.cs b/Shogi/BackEnd/Types/BoardState.cs similarity index 83% rename from Shogi.Contracts/Types/BoardState.cs rename to Shogi/BackEnd/Types/BoardState.cs index ef9b987..ef834db 100644 --- a/Shogi.Contracts/Types/BoardState.cs +++ b/Shogi/BackEnd/Types/BoardState.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Shogi.Contracts.Types; +namespace Shogi.BackEnd.Types; public class BoardState { diff --git a/Shogi.Contracts/Api/Commands/MovePieceCommand.cs b/Shogi/BackEnd/Types/MovePieceCommand.cs similarity index 95% rename from Shogi.Contracts/Api/Commands/MovePieceCommand.cs rename to Shogi/BackEnd/Types/MovePieceCommand.cs index 741fd69..a8ab1b2 100644 --- a/Shogi.Contracts/Api/Commands/MovePieceCommand.cs +++ b/Shogi/BackEnd/Types/MovePieceCommand.cs @@ -1,9 +1,7 @@ -using Shogi.Contracts.Types; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; -namespace Shogi.Contracts.Api.Commands; +namespace Shogi.BackEnd.Types; public partial class MovePieceCommand : IValidatableObject { diff --git a/Shogi/BackEnd/Types/Piece.cs b/Shogi/BackEnd/Types/Piece.cs new file mode 100644 index 0000000..2c7c41c --- /dev/null +++ b/Shogi/BackEnd/Types/Piece.cs @@ -0,0 +1,8 @@ +namespace Shogi.BackEnd.Types; + +public class Piece +{ + public bool IsPromoted { get; set; } + public WhichPiece WhichPiece { get; set; } + public WhichPlayer Owner { get; set; } +} diff --git a/Shogi.Contracts/Types/Session.cs b/Shogi/BackEnd/Types/Session.cs similarity index 74% rename from Shogi.Contracts/Types/Session.cs rename to Shogi/BackEnd/Types/Session.cs index bc67642..e418097 100644 --- a/Shogi.Contracts/Types/Session.cs +++ b/Shogi/BackEnd/Types/Session.cs @@ -1,6 +1,4 @@ -using System; - -namespace Shogi.Contracts.Types; +namespace Shogi.BackEnd.Types; public class Session { @@ -16,5 +14,5 @@ public class Session public Guid SessionId { get; set; } - public BoardState BoardState { get; set; } + public BoardState BoardState { get; set; } = new(); } diff --git a/Shogi.Contracts/Types/SessionMetadata.cs b/Shogi/BackEnd/Types/SessionMetadata.cs similarity index 77% rename from Shogi.Contracts/Types/SessionMetadata.cs rename to Shogi/BackEnd/Types/SessionMetadata.cs index 8b3c2b3..e242ea7 100644 --- a/Shogi.Contracts/Types/SessionMetadata.cs +++ b/Shogi/BackEnd/Types/SessionMetadata.cs @@ -1,6 +1,4 @@ -using System; - -namespace Shogi.Contracts.Types; +namespace Shogi.BackEnd.Types; public class SessionMetadata { diff --git a/Shogi.Contracts/Types/WhichPiece.cs b/Shogi/BackEnd/Types/WhichPiece.cs similarity index 74% rename from Shogi.Contracts/Types/WhichPiece.cs rename to Shogi/BackEnd/Types/WhichPiece.cs index 2a13589..5b174a6 100644 --- a/Shogi.Contracts/Types/WhichPiece.cs +++ b/Shogi/BackEnd/Types/WhichPiece.cs @@ -1,4 +1,4 @@ -namespace Shogi.Contracts.Types; +namespace Shogi.BackEnd.Types; public enum WhichPiece { diff --git a/Shogi.Contracts/Types/WhichPerspective.cs b/Shogi/BackEnd/Types/WhichPlayer.cs similarity index 57% rename from Shogi.Contracts/Types/WhichPerspective.cs rename to Shogi/BackEnd/Types/WhichPlayer.cs index 6016326..6b52b93 100644 --- a/Shogi.Contracts/Types/WhichPerspective.cs +++ b/Shogi/BackEnd/Types/WhichPlayer.cs @@ -1,4 +1,4 @@ -namespace Shogi.Contracts.Types; +namespace Shogi.BackEnd.Types; public enum WhichPlayer { diff --git a/Shogi.UI/Identity/CookieAuthenticationStateProvider.cs b/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs similarity index 99% rename from Shogi.UI/Identity/CookieAuthenticationStateProvider.cs rename to Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs index 648de91..fb5f1ac 100644 --- a/Shogi.UI/Identity/CookieAuthenticationStateProvider.cs +++ b/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs @@ -1,4 +1,4 @@ -namespace Shogi.UI.Identity; +namespace Shogi.FrontEnd.Client; using Microsoft.AspNetCore.Components.Authorization; using System.Net.Http; diff --git a/Shogi.UI/Identity/FormResult.cs b/Shogi/FrontEnd/Client/FormResult.cs similarity index 89% rename from Shogi.UI/Identity/FormResult.cs rename to Shogi/FrontEnd/Client/FormResult.cs index c2bf8ac..6cc9253 100644 --- a/Shogi.UI/Identity/FormResult.cs +++ b/Shogi/FrontEnd/Client/FormResult.cs @@ -1,4 +1,4 @@ -namespace Shogi.UI.Identity; +namespace Shogi.FrontEnd.Client; public class FormResult { diff --git a/Shogi.UI/Shared/GameHubNode.cs b/Shogi/FrontEnd/Client/GameHubNode.cs similarity index 64% rename from Shogi.UI/Shared/GameHubNode.cs rename to Shogi/FrontEnd/Client/GameHubNode.cs index 6d78365..899bd73 100644 --- a/Shogi.UI/Shared/GameHubNode.cs +++ b/Shogi/FrontEnd/Client/GameHubNode.cs @@ -1,28 +1,19 @@ -using Microsoft.AspNetCore.SignalR.Client; -using Shogi.UI.Identity; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.SignalR.Client; -namespace Shogi.UI.Shared; +namespace Shogi.FrontEnd.Client; public class GameHubNode : IAsyncDisposable { private readonly HubConnection hubConnection; - public GameHubNode(IConfiguration configuration) + public GameHubNode(NavigationManager navigationManager) { - var baseUrl = configuration["ShogiApiUrl"]; - if (string.IsNullOrWhiteSpace(baseUrl)) - { - throw new InvalidOperationException("ShogiApiUrl configuration is missing."); - } + var hubUrl = navigationManager.ToAbsoluteUri("/gamehub"); this.hubConnection = new HubConnectionBuilder() - .WithUrl(new Uri(new Uri(baseUrl, UriKind.Absolute), "gamehub"), options => - { - options.HttpMessageHandlerFactory = handler => new CookieCredentialsMessageHandler { InnerHandler = handler }; - options.SkipNegotiation = true; - options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; - }) - .Build(); + .WithUrl(hubUrl) + .Build(); this.hubConnection.Closed += this.HubConnection_Closed; } diff --git a/Shogi.UI/Identity/IAccountManagement.cs b/Shogi/FrontEnd/Client/IAccountManagement.cs similarity index 96% rename from Shogi.UI/Identity/IAccountManagement.cs rename to Shogi/FrontEnd/Client/IAccountManagement.cs index 3f92acc..76bb5d8 100644 --- a/Shogi.UI/Identity/IAccountManagement.cs +++ b/Shogi/FrontEnd/Client/IAccountManagement.cs @@ -1,4 +1,4 @@ -namespace Shogi.UI.Identity; +namespace Shogi.FrontEnd.Client; /// /// Account management services. diff --git a/Shogi.UI/Shared/LocalStorage.cs b/Shogi/FrontEnd/Client/LocalStorage.cs similarity index 97% rename from Shogi.UI/Shared/LocalStorage.cs rename to Shogi/FrontEnd/Client/LocalStorage.cs index 5acbc3b..8439036 100644 --- a/Shogi.UI/Shared/LocalStorage.cs +++ b/Shogi/FrontEnd/Client/LocalStorage.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Shogi.UI.Shared; +namespace Shogi.FrontEnd.Client; public class LocalStorage : ILocalStorage { diff --git a/Shogi/FrontEnd/Client/ShogiService.cs b/Shogi/FrontEnd/Client/ShogiService.cs new file mode 100644 index 0000000..0a2eab5 --- /dev/null +++ b/Shogi/FrontEnd/Client/ShogiService.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Identity; +using Shogi.BackEnd.Application; +using Shogi.BackEnd.Extensions; +using Shogi.BackEnd.Identity; +using Shogi.BackEnd.Repositories; +using Shogi.BackEnd.Types; + +namespace Shogi.FrontEnd.Client; + +/// +/// Service layer for Blazor components to access application functionality directly, +/// without going through HTTP. +/// +public class ShogiService( + ShogiApplication application, + SessionRepository sessionRepository, + QueryRepository queryRepository, + UserManager userManager, + GameHubContext gameHubContext) +{ + public async Task CreateSession(string playerId) + { + var sessionId = Guid.NewGuid(); + var session = new BackEnd.Domains.Aggregates.Session(sessionId, playerId); + + try + { + await sessionRepository.CreateSession(session); + return sessionId; + } + catch + { + return null; + } + } + + public async Task GetSession(string sessionId) + { + var domainSession = await application.ReadSession(sessionId); + if (domainSession is null) return null; + + return new Session + { + BoardState = new BoardState + { + Board = domainSession.Board.BoardState.State.ToContract(), + Player1Hand = domainSession.Board.BoardState.Player1Hand.ToContract(), + Player2Hand = domainSession.Board.BoardState.Player2Hand.ToContract(), + PlayerInCheck = domainSession.Board.BoardState.InCheck?.ToContract(), + WhoseTurn = domainSession.Board.BoardState.WhoseTurn.ToContract(), + Victor = domainSession.Board.BoardState.IsCheckmate + ? domainSession.Board.BoardState.InCheck == BackEnd.Domains.ValueObjects.WhichPlayer.Player1 + ? WhichPlayer.Player2 + : WhichPlayer.Player1 + : null + }, + Player1 = GetUsername(domainSession.Player1), + Player2 = GetUsername(domainSession.Player2), + SessionId = domainSession.Id + }; + } + + public async Task GetAllSessionsMetadata(string? playerId) + { + var dtos = await application.ReadAllSessionMetadatas(playerId ?? string.Empty); + return dtos + .Select(dto => new SessionMetadata + { + Player1 = GetUsername(dto.Player1Id), + Player2 = GetUsername(dto.Player2Id), + SessionId = Guid.Parse(dto.Id), + }) + .ToArray(); + } + + public async Task Move(string playerId, Guid sessionId, MovePieceCommand command) + { + var result = await application.MovePiece(playerId, sessionId.ToString(), command); + return result is Microsoft.AspNetCore.Mvc.NoContentResult; + } + + public async Task JoinSession(string sessionId, string playerId) + { + var result = await application.JoinSession(sessionId, playerId); + return result is Microsoft.AspNetCore.Mvc.OkResult; + } + + public async Task DeleteSession(Guid sessionId, string playerId) + { + var (session, _) = await sessionRepository.ReadSessionAndMoves(sessionId.ToString()); + if (!session.HasValue) return true; + + if (session.Value.Player1Id == playerId) + { + await sessionRepository.DeleteSession(sessionId.ToString()); + return true; + } + + return false; + } + + private string GetUsername(string? userId) + { + if (string.IsNullOrEmpty(userId)) + { + return string.Empty; + } + + return userManager.Users.FirstOrDefault(u => u.Id == userId)?.UserName ?? string.Empty; + } +} diff --git a/Shogi.UI/Identity/UserInfo.cs b/Shogi/FrontEnd/Client/UserInfo.cs similarity index 93% rename from Shogi.UI/Identity/UserInfo.cs rename to Shogi/FrontEnd/Client/UserInfo.cs index 01e7809..ff78ef4 100644 --- a/Shogi.UI/Identity/UserInfo.cs +++ b/Shogi/FrontEnd/Client/UserInfo.cs @@ -1,4 +1,4 @@ -namespace Shogi.UI.Identity; +namespace Shogi.FrontEnd.Client; /// /// User info from identity endpoint to establish claims. diff --git a/Shogi/FrontEnd/Components/App.razor b/Shogi/FrontEnd/Components/App.razor new file mode 100644 index 0000000..df9f838 --- /dev/null +++ b/Shogi/FrontEnd/Components/App.razor @@ -0,0 +1,20 @@ + + + + + + + + Shogi + + + + + + + + + + + + diff --git a/Shogi.UI/Shared/IconButton.razor b/Shogi/FrontEnd/Components/IconButton.razor similarity index 100% rename from Shogi.UI/Shared/IconButton.razor rename to Shogi/FrontEnd/Components/IconButton.razor diff --git a/Shogi.UI/Shared/IconButton.razor.css b/Shogi/FrontEnd/Components/IconButton.razor.css similarity index 100% rename from Shogi.UI/Shared/IconButton.razor.css rename to Shogi/FrontEnd/Components/IconButton.razor.css diff --git a/Shogi.UI/Shared/Icons/ChevronDownIcon.razor b/Shogi/FrontEnd/Components/Icons/ChevronDownIcon.razor similarity index 100% rename from Shogi.UI/Shared/Icons/ChevronDownIcon.razor rename to Shogi/FrontEnd/Components/Icons/ChevronDownIcon.razor diff --git a/Shogi.UI/Shared/Icons/ChevronUpIcon.razor b/Shogi/FrontEnd/Components/Icons/ChevronUpIcon.razor similarity index 100% rename from Shogi.UI/Shared/Icons/ChevronUpIcon.razor rename to Shogi/FrontEnd/Components/Icons/ChevronUpIcon.razor diff --git a/Shogi.UI/Shared/Icons/TrashCanIcon.razor b/Shogi/FrontEnd/Components/Icons/TrashCanIcon.razor similarity index 100% rename from Shogi.UI/Shared/Icons/TrashCanIcon.razor rename to Shogi/FrontEnd/Components/Icons/TrashCanIcon.razor diff --git a/Shogi.UI/Layout/MainLayout.razor b/Shogi/FrontEnd/Components/Layout/MainLayout.razor similarity index 56% rename from Shogi.UI/Layout/MainLayout.razor rename to Shogi/FrontEnd/Components/Layout/MainLayout.razor index 35169e1..bac1397 100644 --- a/Shogi.UI/Layout/MainLayout.razor +++ b/Shogi/FrontEnd/Components/Layout/MainLayout.razor @@ -1,6 +1,6 @@ @inherits LayoutComponentBase -
+
@Body
diff --git a/Shogi.UI/Layout/MainLayout.razor.css b/Shogi/FrontEnd/Components/Layout/MainLayout.razor.css similarity index 100% rename from Shogi.UI/Layout/MainLayout.razor.css rename to Shogi/FrontEnd/Components/Layout/MainLayout.razor.css diff --git a/Shogi.UI/Layout/NavMenu.razor b/Shogi/FrontEnd/Components/Layout/NavMenu.razor similarity index 79% rename from Shogi.UI/Layout/NavMenu.razor rename to Shogi/FrontEnd/Components/Layout/NavMenu.razor index febff03..2086597 100644 --- a/Shogi.UI/Layout/NavMenu.razor +++ b/Shogi/FrontEnd/Components/Layout/NavMenu.razor @@ -1,5 +1,5 @@ @inject NavigationManager navigator -@inject ShogiApi Api +@inject ShogiService Service @* Desktop view *@ @code { + [CascadingParameter] + private Task AuthState { get; set; } = default!; + private bool isExpanded = false; async Task CreateSession() { - var sessionId = await Api.PostSession(); - if (!string.IsNullOrEmpty(sessionId)) + var state = await AuthState; + var userId = state.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (userId is null) return; + + var sessionId = await Service.CreateSession(userId); + if (sessionId.HasValue) { - navigator.NavigateTo($"play/{sessionId}"); + navigator.NavigateTo($"play/{sessionId.Value}"); } } diff --git a/Shogi.UI/Layout/NavMenu.razor.css b/Shogi/FrontEnd/Components/Layout/NavMenu.razor.css similarity index 100% rename from Shogi.UI/Layout/NavMenu.razor.css rename to Shogi/FrontEnd/Components/Layout/NavMenu.razor.css diff --git a/Shogi.UI/Pages/FancyErrorPage.razor b/Shogi/FrontEnd/Components/Pages/FancyErrorPage.razor similarity index 100% rename from Shogi.UI/Pages/FancyErrorPage.razor rename to Shogi/FrontEnd/Components/Pages/FancyErrorPage.razor diff --git a/Shogi.UI/Pages/FancyErrorPage.razor.css b/Shogi/FrontEnd/Components/Pages/FancyErrorPage.razor.css similarity index 100% rename from Shogi.UI/Pages/FancyErrorPage.razor.css rename to Shogi/FrontEnd/Components/Pages/FancyErrorPage.razor.css diff --git a/Shogi.UI/Pages/Home/HomePage.razor b/Shogi/FrontEnd/Components/Pages/Home/HomePage.razor similarity index 98% rename from Shogi.UI/Pages/Home/HomePage.razor rename to Shogi/FrontEnd/Components/Pages/Home/HomePage.razor index 4185bc4..5b9dcdd 100644 --- a/Shogi.UI/Pages/Home/HomePage.razor +++ b/Shogi/FrontEnd/Components/Pages/Home/HomePage.razor @@ -1,6 +1,5 @@ @page "/" -@using Shogi.Contracts.Types @using System.Net.WebSockets @using System.Text diff --git a/Shogi.UI/Pages/Home/HomePage.razor.css b/Shogi/FrontEnd/Components/Pages/Home/HomePage.razor.css similarity index 100% rename from Shogi.UI/Pages/Home/HomePage.razor.css rename to Shogi/FrontEnd/Components/Pages/Home/HomePage.razor.css diff --git a/Shogi.UI/Pages/Home/VisualAids/BishopMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/BishopMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/BishopMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/BishopMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/BoardSetupVisualAid.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/BoardSetupVisualAid.razor similarity index 98% rename from Shogi.UI/Pages/Home/VisualAids/BoardSetupVisualAid.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/BoardSetupVisualAid.razor index 8ad5251..9dc4db1 100644 --- a/Shogi.UI/Pages/Home/VisualAids/BoardSetupVisualAid.razor +++ b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/BoardSetupVisualAid.razor @@ -1,6 +1,4 @@ -@using Shogi.Contracts.Types - -
+
diff --git a/Shogi.UI/Pages/Home/VisualAids/BoardSetupVisualAid.razor.css b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/BoardSetupVisualAid.razor.css similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/BoardSetupVisualAid.razor.css rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/BoardSetupVisualAid.razor.css diff --git a/Shogi.UI/Pages/Home/VisualAids/DragonMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/DragonMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/DragonMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/DragonMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/GoldGeneralMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/GoldGeneralMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/GoldGeneralMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/GoldGeneralMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/HorseMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/HorseMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/HorseMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/HorseMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/KingMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/KingMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/KingMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/KingMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/KnightMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/KnightMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/KnightMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/KnightMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/LanceMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/LanceMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/LanceMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/LanceMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PawnMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PawnMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PawnMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PawnMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PieceMovesVisualAid.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PieceMovesVisualAid.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PieceMovesVisualAid.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PieceMovesVisualAid.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PieceMovesVisualAid.razor.css b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PieceMovesVisualAid.razor.css similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PieceMovesVisualAid.razor.css rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PieceMovesVisualAid.razor.css diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedKnightMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedKnightMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedKnightMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedKnightMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedLanceMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedLanceMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedLanceMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedLanceMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedPawnMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPawnMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedPawnMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPawnMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedPieceVisualAid.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPieceVisualAid.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedPieceVisualAid.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPieceVisualAid.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedPieceVisualAid.razor.css b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPieceVisualAid.razor.css similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedPieceVisualAid.razor.css rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedPieceVisualAid.razor.css diff --git a/Shogi.UI/Pages/Home/VisualAids/PromotedSilverMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedSilverMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/PromotedSilverMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/PromotedSilverMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/RookMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/RookMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/RookMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/RookMoves.razor diff --git a/Shogi.UI/Pages/Home/VisualAids/SilverMoves.razor b/Shogi/FrontEnd/Components/Pages/Home/VisualAids/SilverMoves.razor similarity index 100% rename from Shogi.UI/Pages/Home/VisualAids/SilverMoves.razor rename to Shogi/FrontEnd/Components/Pages/Home/VisualAids/SilverMoves.razor diff --git a/Shogi/FrontEnd/Components/Pages/Home/_Imports.razor b/Shogi/FrontEnd/Components/Pages/Home/_Imports.razor new file mode 100644 index 0000000..b57aff1 --- /dev/null +++ b/Shogi/FrontEnd/Components/Pages/Home/_Imports.razor @@ -0,0 +1 @@ +@using Shogi.FrontEnd.Components.Pages.Home.VisualAids \ No newline at end of file diff --git a/Shogi.UI/Pages/Identity/ForgotPassword.razor b/Shogi/FrontEnd/Components/Pages/Identity/ForgotPassword.razor similarity index 100% rename from Shogi.UI/Pages/Identity/ForgotPassword.razor rename to Shogi/FrontEnd/Components/Pages/Identity/ForgotPassword.razor diff --git a/Shogi.UI/Pages/Identity/ForgotPassword.razor.css b/Shogi/FrontEnd/Components/Pages/Identity/ForgotPassword.razor.css similarity index 100% rename from Shogi.UI/Pages/Identity/ForgotPassword.razor.css rename to Shogi/FrontEnd/Components/Pages/Identity/ForgotPassword.razor.css diff --git a/Shogi.UI/Pages/Identity/LoginPage.razor b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor similarity index 100% rename from Shogi.UI/Pages/Identity/LoginPage.razor rename to Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor diff --git a/Shogi.UI/Pages/Identity/LoginPage.razor.css b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css similarity index 100% rename from Shogi.UI/Pages/Identity/LoginPage.razor.css rename to Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css diff --git a/Shogi.UI/Pages/Identity/LogoutPage.razor b/Shogi/FrontEnd/Components/Pages/Identity/LogoutPage.razor similarity index 100% rename from Shogi.UI/Pages/Identity/LogoutPage.razor rename to Shogi/FrontEnd/Components/Pages/Identity/LogoutPage.razor diff --git a/Shogi.UI/Pages/Identity/RegisterPage.razor b/Shogi/FrontEnd/Components/Pages/Identity/RegisterPage.razor similarity index 100% rename from Shogi.UI/Pages/Identity/RegisterPage.razor rename to Shogi/FrontEnd/Components/Pages/Identity/RegisterPage.razor diff --git a/Shogi.UI/Pages/Identity/RegisterPage.razor.css b/Shogi/FrontEnd/Components/Pages/Identity/RegisterPage.razor.css similarity index 100% rename from Shogi.UI/Pages/Identity/RegisterPage.razor.css rename to Shogi/FrontEnd/Components/Pages/Identity/RegisterPage.razor.css diff --git a/Shogi/FrontEnd/Components/Pages/Play/GameBoard/EmptyGameBoard.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/EmptyGameBoard.razor new file mode 100644 index 0000000..b24e7d0 --- /dev/null +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/EmptyGameBoard.razor @@ -0,0 +1,4 @@ + + +@code { +} diff --git a/Shogi.UI/Pages/Play/GameBoard/GameBoard.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoard.razor similarity index 90% rename from Shogi.UI/Pages/Play/GameBoard/GameBoard.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoard.razor index 1724e1b..a9d722e 100644 --- a/Shogi.UI/Pages/Play/GameBoard/GameBoard.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoard.razor @@ -1,10 +1,8 @@ -@using Shogi.Contracts.Api -@using Shogi.Contracts.Types -@using System.Text.RegularExpressions +@using System.Text.RegularExpressions @using System.Security.Claims @implements IDisposable -@inject ShogiApi ShogiApi +@inject ShogiService Service @inject GameHubNode hubNode @inject NavigationManager navigator @@ -52,7 +50,7 @@ else { if (!string.IsNullOrWhiteSpace(SessionId)) { - this.session = await ShogiApi.GetSession(SessionId); + this.session = await Service.GetSession(SessionId); if (this.session != null) { var state = await authenticationState; diff --git a/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoardPresentation.razor similarity index 99% rename from Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoardPresentation.razor index 21679ab..8bd7339 100644 --- a/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameBoardPresentation.razor @@ -1,5 +1,4 @@ -@using Shogi.Contracts.Types; -@using System.Text.Json; +@using System.Text.Json;
diff --git a/Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameboardPresentation.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/GameboardPresentation.razor.css diff --git a/Shogi.UI/Pages/Play/GameBoard/OpponentName.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/OpponentName.razor similarity index 90% rename from Shogi.UI/Pages/Play/GameBoard/OpponentName.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/OpponentName.razor index da3fc0c..ce82dd6 100644 --- a/Shogi.UI/Pages/Play/GameBoard/OpponentName.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/OpponentName.razor @@ -1,5 +1,4 @@ -@using Shogi.Contracts.Types -
+
@if (IsTurn) { diff --git a/Shogi.UI/Pages/Play/GameBoard/OpponentName.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/OpponentName.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBoard/OpponentName.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/OpponentName.razor.css diff --git a/Shogi.UI/Pages/Play/GameBoard/PlayerName.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/PlayerName.razor similarity index 89% rename from Shogi.UI/Pages/Play/GameBoard/PlayerName.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/PlayerName.razor index 05d961b..615a8dd 100644 --- a/Shogi.UI/Pages/Play/GameBoard/PlayerName.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/PlayerName.razor @@ -1,5 +1,4 @@ -@using Shogi.Contracts.Types -
+
@if (string.IsNullOrEmpty(Name)) { diff --git a/Shogi.UI/Pages/Play/GameBoard/PlayerName.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/PlayerName.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBoard/PlayerName.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/PlayerName.razor.css diff --git a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/SeatedGameBoard.razor similarity index 86% rename from Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/SeatedGameBoard.razor index fc66d1c..198706b 100644 --- a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/SeatedGameBoard.razor @@ -1,8 +1,7 @@ -@using Shogi.Contracts.Api.Commands -@using Shogi.Contracts.Types; @using System.Text.RegularExpressions; @using System.Net; -@inject ShogiApi ShogiApi; +@using System.Security.Claims +@inject ShogiService Service @code { + [CascadingParameter] + private Task AuthState { get; set; } = default!; + [Parameter, EditorRequired] public WhichPlayer Perspective { get; set; } [Parameter, EditorRequired] @@ -43,16 +45,18 @@ private bool showPromotePrompt; private string? moveTo; private bool showError = false; + private string? userId; - protected override void OnParametersSet() + protected override async Task OnParametersSetAsync() { - base.OnParametersSet(); selectedBoardPosition = null; selectedPieceFromHand = null; if (Session == null) { throw new ArgumentException($"{nameof(Session)} cannot be null.", nameof(Session)); } + var state = await AuthState; + userId = state.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; } bool IsWithinPromoteArea(string position) @@ -96,8 +100,7 @@ if (pieceAtPosition is null) { // Placing a piece from the hand to an empty space. - var success = await ShogiApi.Move( - Session.SessionId, + var success = await Service.Move(userId!, Session.SessionId, new MovePieceCommand(selectedPieceFromHand.Value, position)); if (!success) { @@ -123,7 +126,7 @@ } else { - var success = await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, position, false)); + var success = await Service.Move(userId!, Session.SessionId, new MovePieceCommand(selectedBoardPosition, position, false)); if (!success) { selectedBoardPosition = null; @@ -155,7 +158,7 @@ { if (selectedBoardPosition != null && moveTo != null) { - showError = await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, moveTo, shouldPromote)); + showError = await Service.Move(userId!, Session.SessionId, new MovePieceCommand(selectedBoardPosition, moveTo, shouldPromote)); showPromotePrompt = false; return; } @@ -168,3 +171,4 @@ showError = false; } } + diff --git a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/SeatedGameBoard.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/SeatedGameBoard.razor.css diff --git a/Shogi.UI/Pages/Play/GameBoard/SpectatorGameBoard.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/SpectatorGameBoard.razor similarity index 59% rename from Shogi.UI/Pages/Play/GameBoard/SpectatorGameBoard.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBoard/SpectatorGameBoard.razor index fcc8dd2..7a6e941 100644 --- a/Shogi.UI/Pages/Play/GameBoard/SpectatorGameBoard.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBoard/SpectatorGameBoard.razor @@ -1,6 +1,6 @@ -@using Contracts.Types -@using System.Net -@inject ShogiApi ShogiApi +@using System.Net +@using System.Security.Claims +@inject ShogiService Service @code { + [CascadingParameter] + private Task AuthState { get; set; } = default!; + [Parameter] [EditorRequired] public Session Session { get; set; } = default!; @@ -25,7 +28,10 @@ async Task OnClickJoinGame() { - var response = await ShogiApi.PatchJoinGame(Session.SessionId.ToString()); - response.EnsureSuccessStatusCode(); + var state = await AuthState; + var userId = state.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (userId is null) return; + + await Service.JoinSession(Session.SessionId.ToString(), userId); } } diff --git a/Shogi.UI/Pages/Play/GameBrowser.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor similarity index 83% rename from Shogi.UI/Pages/Play/GameBrowser.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor index 81d47fd..7866211 100644 --- a/Shogi.UI/Pages/Play/GameBrowser.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor @@ -1,9 +1,8 @@ -@using Shogi.Contracts.Types; -@using System.ComponentModel.DataAnnotations; +@using System.ComponentModel.DataAnnotations; @using System.Net; @using System.Text.Json; -@inject ShogiApi ShogiApi +@inject ShogiService Service
@@ -37,7 +36,7 @@ async Task FetchSessions() { - var sessions = await ShogiApi.GetAllSessionsMetadata(); + var sessions = await Service.GetAllSessionsMetadata(null); Console.WriteLine("Session count {0}", sessions.Length); if (sessions != null) { diff --git a/Shogi.UI/Pages/Play/GameBrowser.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBrowser.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor.css diff --git a/Shogi.UI/Pages/Play/GameBrowserEntry.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor similarity index 79% rename from Shogi.UI/Pages/Play/GameBrowserEntry.razor rename to Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor index 389db93..ab69b0a 100644 --- a/Shogi.UI/Pages/Play/GameBrowserEntry.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor @@ -1,6 +1,5 @@ -@using Shogi.Contracts.Types - -@inject ShogiApi Api +@inject ShogiService Service +@using System.Security.Claims @if (showDeletePrompt) @@ -46,6 +45,9 @@ @code { + [CascadingParameter] + private Task AuthState { get; set; } = default!; + [Parameter][EditorRequired] public SessionMetadata Session { get; set; } = default!; [Parameter][EditorRequired] public EventCallback OnSessionDeleted { get; set; } private bool showDeletePrompt = false; @@ -59,8 +61,12 @@ async Task DeleteSession() { - var response = await Api.DeleteSession(Session.SessionId); - if (response.IsSuccessStatusCode) + var state = await AuthState; + var userId = state.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (userId is null) return; + + var success = await Service.DeleteSession(Session.SessionId, userId); + if (success) { showDeletePrompt = false; showDeleteError = false; diff --git a/Shogi.UI/Pages/Play/GameBrowserEntry.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GameBrowserEntry.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css diff --git a/Shogi.UI/Pages/Play/GamePiece.razor b/Shogi/FrontEnd/Components/Pages/Play/GamePiece.razor similarity index 94% rename from Shogi.UI/Pages/Play/GamePiece.razor rename to Shogi/FrontEnd/Components/Pages/Play/GamePiece.razor index b4ce0ae..6c09228 100644 --- a/Shogi.UI/Pages/Play/GamePiece.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GamePiece.razor @@ -1,6 +1,4 @@ -@using Shogi.Contracts.Types - -
+
@switch (Piece) { case WhichPiece.Bishop: diff --git a/Shogi.UI/Pages/Play/GamePiece.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GamePiece.razor.css similarity index 100% rename from Shogi.UI/Pages/Play/GamePiece.razor.css rename to Shogi/FrontEnd/Components/Pages/Play/GamePiece.razor.css diff --git a/Shogi.UI/Pages/Play/Pieces/Bishop.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/Bishop.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/Bishop.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/Bishop.razor diff --git a/Shogi.UI/Pages/Play/Pieces/ChallengingKing.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/ChallengingKing.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/ChallengingKing.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/ChallengingKing.razor diff --git a/Shogi.UI/Pages/Play/Pieces/GoldGeneral.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/GoldGeneral.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/GoldGeneral.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/GoldGeneral.razor diff --git a/Shogi.UI/Pages/Play/Pieces/Knight.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/Knight.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/Knight.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/Knight.razor diff --git a/Shogi.UI/Pages/Play/Pieces/Lance.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/Lance.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/Lance.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/Lance.razor diff --git a/Shogi.UI/Pages/Play/Pieces/Pawn.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/Pawn.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/Pawn.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/Pawn.razor diff --git a/Shogi.UI/Pages/Play/Pieces/ReigningKing.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/ReigningKing.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/ReigningKing.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/ReigningKing.razor diff --git a/Shogi.UI/Pages/Play/Pieces/Rook.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/Rook.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/Rook.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/Rook.razor diff --git a/Shogi.UI/Pages/Play/Pieces/SilverGeneral.razor b/Shogi/FrontEnd/Components/Pages/Play/Pieces/SilverGeneral.razor similarity index 100% rename from Shogi.UI/Pages/Play/Pieces/SilverGeneral.razor rename to Shogi/FrontEnd/Components/Pages/Play/Pieces/SilverGeneral.razor diff --git a/Shogi.UI/Pages/Play/PlayPage.razor b/Shogi/FrontEnd/Components/Pages/Play/PlayPage.razor similarity index 100% rename from Shogi.UI/Pages/Play/PlayPage.razor rename to Shogi/FrontEnd/Components/Pages/Play/PlayPage.razor diff --git a/Shogi.UI/Pages/SearchPage.razor b/Shogi/FrontEnd/Components/Pages/SearchPage.razor similarity index 100% rename from Shogi.UI/Pages/SearchPage.razor rename to Shogi/FrontEnd/Components/Pages/SearchPage.razor diff --git a/Shogi.UI/Pages/SearchPage.razor.css b/Shogi/FrontEnd/Components/Pages/SearchPage.razor.css similarity index 100% rename from Shogi.UI/Pages/SearchPage.razor.css rename to Shogi/FrontEnd/Components/Pages/SearchPage.razor.css diff --git a/Shogi.UI/Shared/RotatingCogsSvg.razor b/Shogi/FrontEnd/Components/RotatingCogsSvg.razor similarity index 100% rename from Shogi.UI/Shared/RotatingCogsSvg.razor rename to Shogi/FrontEnd/Components/RotatingCogsSvg.razor diff --git a/Shogi.UI/Shared/RotatingCogsSvg.razor.css b/Shogi/FrontEnd/Components/RotatingCogsSvg.razor.css similarity index 100% rename from Shogi.UI/Shared/RotatingCogsSvg.razor.css rename to Shogi/FrontEnd/Components/RotatingCogsSvg.razor.css diff --git a/Shogi.UI/App.razor b/Shogi/FrontEnd/Components/Routes.razor similarity index 74% rename from Shogi.UI/App.razor rename to Shogi/FrontEnd/Components/Routes.razor index e7359b7..71ddacf 100644 --- a/Shogi.UI/App.razor +++ b/Shogi/FrontEnd/Components/Routes.razor @@ -1,8 +1,8 @@ - - - + + + Not found @@ -11,5 +11,4 @@ - diff --git a/Shogi.UI/Shared/Stretch.razor b/Shogi/FrontEnd/Components/Stretch.razor similarity index 100% rename from Shogi.UI/Shared/Stretch.razor rename to Shogi/FrontEnd/Components/Stretch.razor diff --git a/Shogi.UI/Shared/Stretch.razor.css b/Shogi/FrontEnd/Components/Stretch.razor.css similarity index 100% rename from Shogi.UI/Shared/Stretch.razor.css rename to Shogi/FrontEnd/Components/Stretch.razor.css diff --git a/Shogi.UI/Shared/TemporaryModal.razor b/Shogi/FrontEnd/Components/TemporaryModal.razor similarity index 100% rename from Shogi.UI/Shared/TemporaryModal.razor rename to Shogi/FrontEnd/Components/TemporaryModal.razor diff --git a/Shogi.UI/Shared/TemporaryModal.razor.css b/Shogi/FrontEnd/Components/TemporaryModal.razor.css similarity index 100% rename from Shogi.UI/Shared/TemporaryModal.razor.css rename to Shogi/FrontEnd/Components/TemporaryModal.razor.css diff --git a/Shogi.UI/_Imports.razor b/Shogi/FrontEnd/Components/_Imports.razor similarity index 50% rename from Shogi.UI/_Imports.razor rename to Shogi/FrontEnd/Components/_Imports.razor index 2833a05..efec8a7 100644 --- a/Shogi.UI/_Imports.razor +++ b/Shogi/FrontEnd/Components/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using System.Net.Http.Json @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @@ -6,12 +6,12 @@ @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using Shogi.UI.Identity -@using Shogi.UI.Layout -@using Shogi.UI.Pages.Play -@using Shogi.UI.Pages.Play.GameBoard -@using Shogi.UI.Pages.Play.Pieces -@using Shogi.UI.Shared -@using Shogi.UI.Shared.Icons \ No newline at end of file +@using Shogi.FrontEnd.Client +@using Shogi.FrontEnd.Components +@using Shogi.FrontEnd.Components.Layout +@using Shogi.FrontEnd.Components.Pages.Play +@using Shogi.FrontEnd.Components.Pages.Play.GameBoard +@using Shogi.FrontEnd.Components.Pages.Play.Pieces +@using Shogi.FrontEnd.Components.Icons +@using Shogi.BackEnd.Types diff --git a/Shogi/Program.cs b/Shogi/Program.cs new file mode 100644 index 0000000..972a664 --- /dev/null +++ b/Shogi/Program.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.EntityFrameworkCore; +using Shogi; +using Shogi.BackEnd.Application; +using Shogi.FrontEnd.Components; +using Shogi.BackEnd.Controllers; +using Shogi.BackEnd.Identity; +using Shogi.BackEnd.Repositories; +using Shogi.FrontEnd.Client; + +var builder = WebApplication.CreateBuilder(args); + +// Add Blazor components +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +// Add API controllers +builder.Services + .AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.WriteIndented = true; + }); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Application services +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddHttpClient(); +builder.Services.Configure(builder.Configuration.GetSection("ApiKeys")); + +// Client services for Blazor components +builder.Services.AddTransient(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(sp => sp.GetRequiredService()); +builder.Services.AddScoped(sp => sp.GetRequiredService()); + +AddIdentity(builder, builder.Configuration); +builder.Services.AddSignalR(); +builder.Services.AddResponseCompression(opts => +{ + opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]); +}); + +var app = builder.Build(); + +app.MyMapIdentityApi(builder.Environment); + +if (app.Environment.IsDevelopment()) +{ + app.UseHttpsRedirection(); +} +else +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); + app.UseResponseCompression(); +} + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.UseSwagger(); +app.UseSwaggerUI(options => options.DocumentTitle = "Shogi API"); +app.UseAuthorization(); + +app.MapControllers(); +app.MapHub("/gamehub"); +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); + +static void AddIdentity(WebApplicationBuilder builder, ConfigurationManager configuration) +{ + builder.Services + .AddAuthorizationBuilder() + .AddPolicy("Admin", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireAssertion(context => context.User?.Identity?.Name switch + { + "Hauth@live.com" => true, + "aat-account" => true, + _ => false + }); + }); + + builder.Services + .AddDbContext(options => + { + var cs = configuration.GetConnectionString("ShogiDatabase") ?? throw new InvalidOperationException("Database not configured."); + options.UseSqlServer(cs); + }) + .AddIdentityApiEndpoints(options => + { + options.SignIn.RequireConfirmedEmail = true; + options.User.RequireUniqueEmail = true; + }) + .AddEntityFrameworkStores(); + + builder.Services.ConfigureApplicationCookie(options => + { + options.SlidingExpiration = true; + options.ExpireTimeSpan = TimeSpan.FromDays(3); + }); +} + +// Make Program accessible for WebApplicationFactory in integration tests +public partial class Program { } diff --git a/Shogi.Api/Properties/PublishProfiles/FolderProfile.pubxml b/Shogi/Properties/PublishProfiles/FolderProfile.pubxml similarity index 100% rename from Shogi.Api/Properties/PublishProfiles/FolderProfile.pubxml rename to Shogi/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/Shogi.Api/Properties/ServiceDependencies/local/secrets1.arm.json b/Shogi/Properties/ServiceDependencies/local/secrets1.arm.json similarity index 100% rename from Shogi.Api/Properties/ServiceDependencies/local/secrets1.arm.json rename to Shogi/Properties/ServiceDependencies/local/secrets1.arm.json diff --git a/Shogi/Properties/launchSettings.json b/Shogi/Properties/launchSettings.json new file mode 100644 index 0000000..98dbd51 --- /dev/null +++ b/Shogi/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ +"profiles": { + "Kestrel": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "VaultUri": "https://gameboardshogiuisocketsv.vault.azure.net/", + "AZURE_USERNAME": "Hauth@live.com" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + }, + "Swagger": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "VaultUri": "https://gameboardshogiuisocketsv.vault.azure.net/", + "AZURE_USERNAME": "Hauth@live.com" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } +}, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50728/", + "sslPort": 44315 + } + } +} \ No newline at end of file diff --git a/Shogi.Api/Properties/serviceDependencies.json b/Shogi/Properties/serviceDependencies.json similarity index 100% rename from Shogi.Api/Properties/serviceDependencies.json rename to Shogi/Properties/serviceDependencies.json diff --git a/Shogi.Api/Properties/serviceDependencies.local.json b/Shogi/Properties/serviceDependencies.local.json similarity index 100% rename from Shogi.Api/Properties/serviceDependencies.local.json rename to Shogi/Properties/serviceDependencies.local.json diff --git a/Shogi.Api/Readme.md b/Shogi/Readme.md similarity index 100% rename from Shogi.Api/Readme.md rename to Shogi/Readme.md diff --git a/Shogi/Shogi.csproj b/Shogi/Shogi.csproj new file mode 100644 index 0000000..f20c609 --- /dev/null +++ b/Shogi/Shogi.csproj @@ -0,0 +1,90 @@ + + + + net10.0 + true + 5 + enable + False + False + enable + 973a1f5f-ef25-4f1c-a24d-b0fc7d016ab8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Shogi.Api/appsettings.Development.json b/Shogi/appsettings.Development.json similarity index 100% rename from Shogi.Api/appsettings.Development.json rename to Shogi/appsettings.Development.json diff --git a/Shogi.Api/appsettings.json b/Shogi/appsettings.json similarity index 90% rename from Shogi.Api/appsettings.json rename to Shogi/appsettings.json index 1946ca8..6108118 100644 --- a/Shogi.Api/appsettings.json +++ b/Shogi/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "ShogiDatabase": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Shogi;Integrated Security=True;Application Name=Shogi.Api" + "ShogiDatabase": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Shogi;Integrated Security=True;Application Name=Shogi" }, "Logging": { "LogLevel": { diff --git a/Shogi.UI/wwwroot/css/app.css b/Shogi/wwwroot/css/app.css similarity index 100% rename from Shogi.UI/wwwroot/css/app.css rename to Shogi/wwwroot/css/app.css diff --git a/Shogi.UI/wwwroot/css/themes.css b/Shogi/wwwroot/css/themes.css similarity index 100% rename from Shogi.UI/wwwroot/css/themes.css rename to Shogi/wwwroot/css/themes.css diff --git a/Shogi.UI/wwwroot/favicon.ico b/Shogi/wwwroot/favicon.ico similarity index 100% rename from Shogi.UI/wwwroot/favicon.ico rename to Shogi/wwwroot/favicon.ico diff --git a/Shogi.UI/wwwroot/svgs/camera-reels.svg b/Shogi/wwwroot/svgs/camera-reels.svg similarity index 100% rename from Shogi.UI/wwwroot/svgs/camera-reels.svg rename to Shogi/wwwroot/svgs/camera-reels.svg diff --git a/Tests/AcceptanceTests/ApiTests.cs b/Tests/AcceptanceTests/ApiTests.cs index b069bb4..8d4824e 100644 --- a/Tests/AcceptanceTests/ApiTests.cs +++ b/Tests/AcceptanceTests/ApiTests.cs @@ -1,16 +1,11 @@ -using FluentAssertions.Execution; using Shogi.AcceptanceTests.TestSetup; -using Shogi.Contracts.Api.Commands; -using Shogi.Contracts.Types; +using Shogi.Types; using System.Net; using System.Net.Http.Json; -using Xunit.Abstractions; namespace Shogi.AcceptanceTests; -//#pragma warning disable xUnit1033 // There is a bug which provides a false positive of xUnit1033. -//#pragma warning restore xUnit1033 -public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClassFixture +public class ApiTests(AatTestFixture fixture) : IClassFixture { private readonly HttpClient httpClient = fixture.HttpClient; private readonly HttpClient player2HttpClient = fixture.OtherHttpClient; @@ -28,16 +23,16 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas var response = await this.ReadTestSession(); // Assert - response.Should().NotBeNull(); - response!.BoardState.Board.Should().NotBeEmpty(); + Assert.NotNull(response); + Assert.NotEmpty(response!.BoardState.Board); ValidateBoard(response.BoardState.Board); - response.BoardState.Player1Hand.Should().BeEmpty(); - response.BoardState.Player2Hand.Should().BeEmpty(); - response.BoardState.PlayerInCheck.Should().BeNull(); - response.BoardState.WhoseTurn.Should().Be(WhichPlayer.Player1); - response.Player1.Should().NotBeNull(); - response.Player2.Should().BeNullOrEmpty(); - response.SessionId.Should().NotBeEmpty(); + Assert.Empty(response.BoardState.Player1Hand); + Assert.Empty(response.BoardState.Player2Hand); + Assert.Null(response.BoardState.PlayerInCheck); + Assert.Equal(WhichPlayer.Player1, response.BoardState.WhoseTurn); + Assert.NotNull(response.Player1); + Assert.True(string.IsNullOrEmpty(response.Player2)); + Assert.NotEqual(Guid.Empty, response.SessionId); } finally { @@ -47,176 +42,175 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas 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); + Assert.Equal(WhichPiece.Lance, board["A1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["A1"]!.Owner); + Assert.False(board["A1"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["B1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B1"]!.Owner); + Assert.False(board["B1"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["C1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["C1"]!.Owner); + Assert.False(board["C1"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["D1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["D1"]!.Owner); + Assert.False(board["D1"]!.IsPromoted); + Assert.Equal(WhichPiece.King, board["E1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["E1"]!.Owner); + Assert.False(board["E1"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["F1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["F1"]!.Owner); + Assert.False(board["F1"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["G1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["G1"]!.Owner); + Assert.False(board["G1"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["H1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H1"]!.Owner); + Assert.False(board["H1"]!.IsPromoted); + Assert.Equal(WhichPiece.Lance, board["I1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["I1"]!.Owner); + Assert.False(board["I1"]!.IsPromoted); - 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(); + Assert.Null(board["A2"]); + Assert.Equal(WhichPiece.Bishop, board["B2"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B2"]!.Owner); + Assert.False(board["B2"]!.IsPromoted); + Assert.Null(board["C2"]); + Assert.Null(board["D2"]); + Assert.Null(board["E2"]); + Assert.Null(board["F2"]); + Assert.Null(board["G2"]); + Assert.Equal(WhichPiece.Rook, board["H2"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H2"]!.Owner); + Assert.False(board["H2"]!.IsPromoted); + Assert.Null(board["I2"]); - 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); + Assert.Equal(WhichPiece.Pawn, board["A3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["A3"]!.Owner); + Assert.False(board["A3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["B3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B3"]!.Owner); + Assert.False(board["B3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["C3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["C3"]!.Owner); + Assert.False(board["C3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["D3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["D3"]!.Owner); + Assert.False(board["D3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["E3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["E3"]!.Owner); + Assert.False(board["E3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["F3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["F3"]!.Owner); + Assert.False(board["F3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["G3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["G3"]!.Owner); + Assert.False(board["G3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["H3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H3"]!.Owner); + Assert.False(board["H3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["I3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["I3"]!.Owner); + Assert.False(board["I3"]!.IsPromoted); - 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(); + Assert.Null(board["A4"]); + Assert.Null(board["B4"]); + Assert.Null(board["C4"]); + Assert.Null(board["D4"]); + Assert.Null(board["E4"]); + Assert.Null(board["F4"]); + Assert.Null(board["G4"]); + Assert.Null(board["H4"]); + Assert.Null(board["I4"]); - 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(); + Assert.Null(board["A5"]); + Assert.Null(board["B5"]); + Assert.Null(board["C5"]); + Assert.Null(board["D5"]); + Assert.Null(board["E5"]); + Assert.Null(board["F5"]); + Assert.Null(board["G5"]); + Assert.Null(board["H5"]); + Assert.Null(board["I5"]); - 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(); + Assert.Null(board["A6"]); + Assert.Null(board["B6"]); + Assert.Null(board["C6"]); + Assert.Null(board["D6"]); + Assert.Null(board["E6"]); + Assert.Null(board["F6"]); + Assert.Null(board["G6"]); + Assert.Null(board["H6"]); + Assert.Null(board["I6"]); - 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); + Assert.Equal(WhichPiece.Pawn, board["A7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["A7"]!.Owner); + Assert.False(board["A7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["B7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B7"]!.Owner); + Assert.False(board["B7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["C7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["C7"]!.Owner); + Assert.False(board["C7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["D7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["D7"]!.Owner); + Assert.False(board["D7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["E7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["E7"]!.Owner); + Assert.False(board["E7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["F7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["F7"]!.Owner); + Assert.False(board["F7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["G7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["G7"]!.Owner); + Assert.False(board["G7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["H7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H7"]!.Owner); + Assert.False(board["H7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["I7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["I7"]!.Owner); + Assert.False(board["I7"]!.IsPromoted); - 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(); + Assert.Null(board["A8"]); + Assert.Equal(WhichPiece.Rook, board["B8"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B8"]!.Owner); + Assert.False(board["B8"]!.IsPromoted); + Assert.Null(board["C8"]); + Assert.Null(board["D8"]); + Assert.Null(board["E8"]); + Assert.Null(board["F8"]); + Assert.Null(board["G8"]); + Assert.Equal(WhichPiece.Bishop, board["H8"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H8"]!.Owner); + Assert.False(board["H8"]!.IsPromoted); + Assert.Null(board["I8"]); - 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); + Assert.Equal(WhichPiece.Lance, board["A9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["A9"]!.Owner); + Assert.False(board["A9"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["B9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B9"]!.Owner); + Assert.False(board["B9"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["C9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["C9"]!.Owner); + Assert.False(board["C9"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["D9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["D9"]!.Owner); + Assert.False(board["D9"]!.IsPromoted); + Assert.Equal(WhichPiece.King, board["E9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["E9"]!.Owner); + Assert.False(board["E9"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["F9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["F9"]!.Owner); + Assert.False(board["F9"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["G9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["G9"]!.Owner); + Assert.False(board["G9"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["H9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H9"]!.Owner); + Assert.False(board["H9"]!.IsPromoted); + Assert.Equal(WhichPiece.Lance, board["I9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["I9"]!.Owner); + Assert.False(board["I9"]!.IsPromoted); } } @@ -231,12 +225,11 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas // Act var readAllResponse = await this.httpClient.GetFromJsonAsync( - new Uri("Sessions", UriKind.Relative), - Contracts.ShogiApiJsonSerializerSettings.SystemTextJsonSerializerOptions); + new Uri("Sessions", UriKind.Relative)); // Assert - readAllResponse.Should().NotBeNull(); - readAllResponse!.First().SessionId.Should().Be(testSession.SessionId); + Assert.NotNull(readAllResponse); + Assert.Equal(testSession.SessionId, readAllResponse!.First().SessionId); } finally { @@ -257,9 +250,9 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas var joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null); // Assert - joinResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, joinResponse.StatusCode); var readSessionResponse = await this.ReadTestSession(); - readSessionResponse.Player2.Should().NotBeNullOrEmpty(); + Assert.False(string.IsNullOrEmpty(readSessionResponse.Player2)); } finally { @@ -275,15 +268,15 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas // Arrange await this.SetupTestSession(); var joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null); - joinResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, joinResponse.StatusCode); var readSessionResponse = await this.ReadTestSession(); - readSessionResponse.Player2.Should().NotBeNull(); + Assert.NotNull(readSessionResponse.Player2); // Act joinResponse = await this.player2HttpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Join", UriKind.Relative), null); // Assert - joinResponse.StatusCode.Should().Be(HttpStatusCode.Conflict); + Assert.Equal(HttpStatusCode.Conflict, joinResponse.StatusCode); } finally { @@ -306,16 +299,16 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas // Act var response = await this.httpClient.PatchAsync(new Uri($"Sessions/{this.sessionId}/Move", UriKind.Relative), JsonContent.Create(movePawnCommand)); - response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync()); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); // Assert var session = await this.ReadTestSession(); - session.BoardState.Board.Should().ContainKey("A3"); - session.BoardState.Board["A3"].Should().BeNull(); - session.BoardState.Board["A4"].Should().NotBeNull(); - session.BoardState.Board["A4"]!.IsPromoted.Should().BeFalse(); - session.BoardState.Board["A4"]!.Owner.Should().Be(WhichPlayer.Player1); - session.BoardState.Board["A4"]!.WhichPiece.Should().Be(WhichPiece.Pawn); + Assert.Contains("A3", session.BoardState.Board.Keys); + Assert.Null(session.BoardState.Board["A3"]); + Assert.NotNull(session.BoardState.Board["A4"]); + Assert.False(session.BoardState.Board["A4"]!.IsPromoted); + Assert.Equal(WhichPlayer.Player1, session.BoardState.Board["A4"]!.Owner); + Assert.Equal(WhichPiece.Pawn, session.BoardState.Board["A4"]!.WhichPiece); } finally { @@ -327,7 +320,7 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas private async Task SetupTestSession() { var createResponse = await this.httpClient.PostAsync(new Uri("Sessions", UriKind.Relative), null); - createResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); this.sessionId = await createResponse.Content.ReadAsStringAsync(); } @@ -339,7 +332,6 @@ public class ApiTests(AatTestFixture fixture, ITestOutputHelper console) : IClas private async Task DeleteTestSession() { var response = await this.httpClient.DeleteAsync(new Uri($"Sessions/{Uri.EscapeDataString(this.sessionId)}", UriKind.Relative)); - response.StatusCode.Should().Be(HttpStatusCode.NoContent, because: await response.Content.ReadAsStringAsync()); - + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } -} \ No newline at end of file +} diff --git a/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj index 6df2195..76f300d 100644 --- a/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj +++ b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj @@ -1,51 +1,31 @@  - net10.0 - enable - enable + net10.0 + enable + enable - false - 96d6281d-a75b-4181-b535-ea34b26dc8a2 - true + false + true - - - - - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj.Backup.tmp b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj.Backup.tmp new file mode 100644 index 0000000..60683ba --- /dev/null +++ b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj.Backup.tmp @@ -0,0 +1,33 @@ + + + + net10.0 + enable + enable + + false + 96d6281d-a75b-4181-b535-ea34b26dc8a2 + true + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/Tests/AcceptanceTests/TestSetup/AatTestFixture.cs b/Tests/AcceptanceTests/TestSetup/AatTestFixture.cs index 205b92a..c428c29 100644 --- a/Tests/AcceptanceTests/TestSetup/AatTestFixture.cs +++ b/Tests/AcceptanceTests/TestSetup/AatTestFixture.cs @@ -1,114 +1,105 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Shogi.Identity; using System.Net.Http.Json; using System.Text.Json; namespace Shogi.AcceptanceTests.TestSetup; /// -/// Acceptance Test fixture for tests which assert features for Microsoft accounts. +/// Integration test fixture using WebApplicationFactory for in-process testing. /// -public class AatTestFixture : IAsyncLifetime, IDisposable +public class AatTestFixture : WebApplicationFactory, IAsyncLifetime { protected static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web); - private readonly string testAccountPassword; - private bool disposedValue; + private const string TestPassword = "TestPassword123!"; - public AatTestFixture() + public HttpClient HttpClient { get; private set; } = null!; + public HttpClient OtherHttpClient { get; private set; } = null!; + + protected override void ConfigureWebHost(IWebHostBuilder builder) { - this.Configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddJsonFile("appsettings.Development.json", optional: true) - .Build(); + builder.UseEnvironment("Development"); - var baseUrl = this.Configuration["ServiceUrl"] ?? throw new InvalidOperationException(); - this.HttpClient = new HttpClient + builder.ConfigureServices(services => { - BaseAddress = new Uri(baseUrl, UriKind.Absolute) - }; - this.OtherHttpClient = new HttpClient - { - BaseAddress = new Uri(baseUrl, UriKind.Absolute) - }; + // Remove the existing DbContext registration + var descriptor = services.SingleOrDefault( + d => d.ServiceType == typeof(DbContextOptions)); - this.testAccountPassword = this.Configuration["TestUserPassword"]!; - if (string.IsNullOrWhiteSpace(this.testAccountPassword)) - { - throw new InvalidOperationException("TestUserPassword is not configured."); - } - - } - - public IConfiguration Configuration { get; private set; } - public HttpClient HttpClient { get; } - public HttpClient OtherHttpClient { get; } - - protected async Task LoginToTestAccounts() - { - var response = await this.HttpClient.PostAsJsonAsync( - RelativeUri("login"), - new + if (descriptor != null) { - email = "aat-account", - password = this.testAccountPassword, - }, - options: SerializerOptions); + services.Remove(descriptor); + } - response.IsSuccessStatusCode.Should().BeTrue(because: "The test account should exist. If it does not, use the /Account/TestAccount route to create it."); - - var bearerToken = (await response.Content.ReadFromJsonAsync())?.AccessToken; - this.HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken); - - response = await this.HttpClient.PostAsJsonAsync( - RelativeUri("login"), - new + // Add in-memory database for testing + services.AddDbContext(options => { - email = "aat-account-2", - password = this.testAccountPassword, - }, - options: SerializerOptions); + options.UseInMemoryDatabase("IntegrationTestDb_" + Guid.NewGuid().ToString()); + }); - response.IsSuccessStatusCode.Should().BeTrue(because: "The test account should exist. If it does not, use the /Account/TestAccount route to create it."); - - bearerToken = (await response.Content.ReadFromJsonAsync())?.AccessToken; - this.OtherHttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken); + // Ensure the database is created and seeded + var sp = services.BuildServiceProvider(); + using var scope = sp.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + }); } public async Task InitializeAsync() { - await this.LoginToTestAccounts(); + this.HttpClient = this.CreateClient(); + this.OtherHttpClient = this.CreateClient(); + + await this.SetupTestAccountsAndLogin(); } - protected virtual void Dispose(bool disposing) + private async Task SetupTestAccountsAndLogin() { - if (!this.disposedValue) - { - if (disposing) - { - this.HttpClient.Dispose(); - } + // Register and login first test account + await RegisterAndLogin(this.HttpClient, "aat-account@test.com", TestPassword); - this.disposedValue = true; + // Register and login second test account + await RegisterAndLogin(this.OtherHttpClient, "aat-account-2@test.com", TestPassword); + } + + private static async Task RegisterAndLogin(HttpClient client, string email, string password) + { + // Try to register (may already exist) + await client.PostAsJsonAsync( + new Uri("register", UriKind.Relative), + new { email, password }, + options: SerializerOptions); + + // Login + var loginResponse = await client.PostAsJsonAsync( + new Uri("login", UriKind.Relative), + new { email, password }, + options: SerializerOptions); + + if (loginResponse.IsSuccessStatusCode) + { + var tokenResponse = await loginResponse.Content.ReadFromJsonAsync(SerializerOptions); + if (tokenResponse?.AccessToken != null) + { + client.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); + } } } - public Task DisposeAsync() + async Task IAsyncLifetime.DisposeAsync() { - this.Dispose(true); - return Task.CompletedTask; + this.HttpClient?.Dispose(); + this.OtherHttpClient?.Dispose(); + await base.DisposeAsync(); } - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - protected static Uri RelativeUri(string s) => new(s, UriKind.Relative); - private class LoginResponse { public string AccessToken { get; set; } = string.Empty; } - } diff --git a/Tests/AcceptanceTests/Usings.cs b/Tests/AcceptanceTests/Usings.cs index 7fef4b0..8c927eb 100644 --- a/Tests/AcceptanceTests/Usings.cs +++ b/Tests/AcceptanceTests/Usings.cs @@ -1,2 +1 @@ -global using Xunit; -global using FluentAssertions; \ No newline at end of file +global using Xunit; \ No newline at end of file diff --git a/Tests/AcceptanceTests/appsettings.Development.json b/Tests/AcceptanceTests/appsettings.Development.json deleted file mode 100644 index f6aa32c..0000000 --- a/Tests/AcceptanceTests/appsettings.Development.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TestUserPassword": "I'mAToysRUsK1d" -} diff --git a/Tests/AcceptanceTests/appsettings.json b/Tests/AcceptanceTests/appsettings.json deleted file mode 100644 index f217a91..0000000 --- a/Tests/AcceptanceTests/appsettings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ServiceUrl": "https://localhost:5001", - "TestUserPassword": "" -} \ No newline at end of file diff --git a/Tests/E2ETests/E2ETests.csproj b/Tests/E2ETests/E2ETests.csproj deleted file mode 100644 index 74fdf80..0000000 --- a/Tests/E2ETests/E2ETests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net10.0 - enable - enable - - false - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/Tests/E2ETests/MicrosoftLoginTests.cs b/Tests/E2ETests/MicrosoftLoginTests.cs deleted file mode 100644 index cb17a53..0000000 --- a/Tests/E2ETests/MicrosoftLoginTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Playwright; -using Microsoft.Playwright.NUnit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace E2ETests; - -[Parallelizable(ParallelScope.Self)] -[TestFixture] -public class MicrosoftLoginTests : PageTest -{ - - [SetUp] - public async void Init() - { - - } - - [Test] - public async Task Test1() - { - await Page.GotoAsync("https://lucaserver.space/shogi", new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle }); - var loginButton = Page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Log in" }); - await loginButton.ClickAsync(); - //await Page.WaitForURLAsync() - } -} \ No newline at end of file diff --git a/Tests/E2ETests/PlaywriteExample.cs b/Tests/E2ETests/PlaywriteExample.cs deleted file mode 100644 index d71891d..0000000 --- a/Tests/E2ETests/PlaywriteExample.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Playwright; -using Microsoft.Playwright.NUnit; -using System.Text.RegularExpressions; - -namespace E2ETests; - -[Parallelizable(ParallelScope.Self)] -[TestFixture] -public class PlaywriteExample : PageTest -{ - [Test] - public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage() - { - await Page.GotoAsync("https://playwright.dev"); - - // Expect a title "to contain" a substring. - await Expect(Page).ToHaveTitleAsync(new Regex("Playwright")); - - // create a locator - var getStarted = Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }); - - // Expect an attribute "to be strictly equal" to the value. - await Expect(getStarted).ToHaveAttributeAsync("href", "/docs/intro"); - - // Click the get started link. - await getStarted.ClickAsync(); - - // Expects the URL to contain intro. - await Expect(Page).ToHaveURLAsync(new Regex(".*intro")); - } -} \ No newline at end of file diff --git a/Tests/E2ETests/Usings.cs b/Tests/E2ETests/Usings.cs deleted file mode 100644 index cefced4..0000000 --- a/Tests/E2ETests/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using NUnit.Framework; \ No newline at end of file diff --git a/Tests/E2ETests/appsettings.json b/Tests/E2ETests/appsettings.json deleted file mode 100644 index 0db3279..0000000 --- a/Tests/E2ETests/appsettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} diff --git a/Tests/UnitTests/Extensions.cs b/Tests/UnitTests/Extensions.cs index f9486c1..faa6f70 100644 --- a/Tests/UnitTests/Extensions.cs +++ b/Tests/UnitTests/Extensions.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.ValueObjects; +using Shogi.Domains.ValueObjects; using System; using System.Text; diff --git a/Tests/UnitTests/NotationShould.cs b/Tests/UnitTests/NotationShould.cs index 577b8a4..2d213fc 100644 --- a/Tests/UnitTests/NotationShould.cs +++ b/Tests/UnitTests/NotationShould.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD; +using Shogi.Domains.YetToBeAssimilatedIntoDDD; using System.Numerics; namespace UnitTests; @@ -8,18 +8,18 @@ public class NotationShould [Fact] public void ConvertFromNotationToVector() { - Notation.FromBoardNotation("A1").Should().Be(new Vector2(0, 0)); - Notation.FromBoardNotation("E5").Should().Be(new Vector2(4, 4)); - Notation.FromBoardNotation("I9").Should().Be(new Vector2(8, 8)); - Notation.FromBoardNotation("C3").Should().Be(new Vector2(2, 2)); + Assert.Equal(new Vector2(0, 0), Notation.FromBoardNotation("A1")); + Assert.Equal(new Vector2(4, 4), Notation.FromBoardNotation("E5")); + Assert.Equal(new Vector2(8, 8), Notation.FromBoardNotation("I9")); + Assert.Equal(new Vector2(2, 2), Notation.FromBoardNotation("C3")); } [Fact] public void ConvertFromVectorToNotation() { - Notation.ToBoardNotation(new Vector2(0, 0)).Should().Be("A1"); - Notation.ToBoardNotation(new Vector2(4, 4)).Should().Be("E5"); - Notation.ToBoardNotation(new Vector2(8, 8)).Should().Be("I9"); - Notation.ToBoardNotation(new Vector2(2, 2)).Should().Be("C3"); + Assert.Equal("A1", Notation.ToBoardNotation(new Vector2(0, 0))); + Assert.Equal("E5", Notation.ToBoardNotation(new Vector2(4, 4))); + Assert.Equal("I9", Notation.ToBoardNotation(new Vector2(8, 8))); + Assert.Equal("C3", Notation.ToBoardNotation(new Vector2(2, 2))); } } diff --git a/Tests/UnitTests/RookShould.cs b/Tests/UnitTests/RookShould.cs index 5d0ab72..89ce99a 100644 --- a/Tests/UnitTests/RookShould.cs +++ b/Tests/UnitTests/RookShould.cs @@ -1,5 +1,6 @@ -using Shogi.Domain.ValueObjects; -using Shogi.Domain.ValueObjects.Movement; +using Shogi.Domains.ValueObjects; +using Shogi.Domains.ValueObjects.Movement; +using System.Linq; using System.Numerics; namespace UnitTests; @@ -21,11 +22,11 @@ public class RookShould public void Player1_HasCorrectMoveSet() { var moveSet = rook1.MoveSet; - moveSet.Should().HaveCount(4); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Forward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Left, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Right, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Backward, Distance.MultiStep)); + Assert.Equal(4, moveSet.Count); + Assert.Contains(new Path(Direction.Forward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Left, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Right, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Backward, Distance.MultiStep), moveSet); } [Fact] @@ -33,30 +34,30 @@ public class RookShould { // Arrange rook1.Promote(); - rook1.IsPromoted.Should().BeTrue(); + Assert.True(rook1.IsPromoted); // Assert var moveSet = rook1.MoveSet; - moveSet.Should().HaveCount(8); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Forward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Left, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Right, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Backward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.ForwardLeft, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.BackwardLeft, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.ForwardRight, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.BackwardRight, Distance.OneStep)); + Assert.Equal(8, moveSet.Count); + Assert.Contains(new Path(Direction.Forward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Left, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Right, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Backward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.ForwardLeft, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.BackwardLeft, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.ForwardRight, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.BackwardRight, Distance.OneStep), moveSet); } [Fact] public void Player2_HasCorrectMoveSet() { var moveSet = rook2.MoveSet; - moveSet.Should().HaveCount(4); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Forward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Left, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Right, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Backward, Distance.MultiStep)); + Assert.Equal(4, moveSet.Count); + Assert.Contains(new Path(Direction.Forward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Left, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Right, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Backward, Distance.MultiStep), moveSet); } [Fact] @@ -64,23 +65,22 @@ public class RookShould { // Arrange rook2.Promote(); - rook2.IsPromoted.Should().BeTrue(); + Assert.True(rook2.IsPromoted); // Assert var moveSet = rook2.MoveSet; - moveSet.Should().HaveCount(8); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Forward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Left, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Right, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.Backward, Distance.MultiStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.ForwardLeft, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.BackwardLeft, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.ForwardRight, Distance.OneStep)); - moveSet.Should().ContainEquivalentOf(new Path(Direction.BackwardRight, Distance.OneStep)); + Assert.Equal(8, moveSet.Count); + Assert.Contains(new Path(Direction.Forward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Left, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Right, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.Backward, Distance.MultiStep), moveSet); + Assert.Contains(new Path(Direction.ForwardLeft, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.BackwardLeft, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.ForwardRight, Distance.OneStep), moveSet); + Assert.Contains(new Path(Direction.BackwardRight, Distance.OneStep), moveSet); } - - } + private readonly Rook rookPlayer1; public RookShould() @@ -91,11 +91,11 @@ public class RookShould [Fact] public void Promote() { - rookPlayer1.IsPromoted.Should().BeFalse(); - rookPlayer1.CanPromote.Should().BeTrue(); + Assert.False(rookPlayer1.IsPromoted); + Assert.True(rookPlayer1.CanPromote); rookPlayer1.Promote(); - rookPlayer1.IsPromoted.Should().BeTrue(); - rookPlayer1.CanPromote.Should().BeFalse(); + Assert.True(rookPlayer1.IsPromoted); + Assert.False(rookPlayer1.CanPromote); } [Fact] @@ -104,15 +104,15 @@ public class RookShould Vector2 start = new(0, 0); Vector2 end = new(0, 5); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeFalse(); - steps.Should().HaveCount(5); - steps.Should().Contain(new Vector2(0, 1)); - steps.Should().Contain(new Vector2(0, 2)); - steps.Should().Contain(new Vector2(0, 3)); - steps.Should().Contain(new Vector2(0, 4)); - steps.Should().Contain(new Vector2(0, 5)); + Assert.False(rookPlayer1.IsPromoted); + Assert.Equal(5, steps.Count); + Assert.Contains(new Vector2(0, 1), steps); + Assert.Contains(new Vector2(0, 2), steps); + Assert.Contains(new Vector2(0, 3), steps); + Assert.Contains(new Vector2(0, 4), steps); + Assert.Contains(new Vector2(0, 5), steps); } [Fact] @@ -121,10 +121,10 @@ public class RookShould Vector2 start = new(0, 0); Vector2 end = new(1, 1); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeFalse(); - steps.Should().BeEmpty(); + Assert.False(rookPlayer1.IsPromoted); + Assert.Empty(steps); } [Fact] @@ -134,15 +134,15 @@ public class RookShould Vector2 end = new(0, 5); rookPlayer1.Promote(); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeTrue(); - steps.Should().HaveCount(5); - steps.Should().Contain(new Vector2(0, 1)); - steps.Should().Contain(new Vector2(0, 2)); - steps.Should().Contain(new Vector2(0, 3)); - steps.Should().Contain(new Vector2(0, 4)); - steps.Should().Contain(new Vector2(0, 5)); + Assert.True(rookPlayer1.IsPromoted); + Assert.Equal(5, steps.Count); + Assert.Contains(new Vector2(0, 1), steps); + Assert.Contains(new Vector2(0, 2), steps); + Assert.Contains(new Vector2(0, 3), steps); + Assert.Contains(new Vector2(0, 4), steps); + Assert.Contains(new Vector2(0, 5), steps); } [Fact] @@ -152,11 +152,11 @@ public class RookShould Vector2 end = new(1, 1); rookPlayer1.Promote(); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeTrue(); - steps.Should().HaveCount(1); - steps.Should().Contain(new Vector2(1, 1)); + Assert.True(rookPlayer1.IsPromoted); + Assert.Single(steps); + Assert.Contains(new Vector2(1, 1), steps); } [Fact] @@ -165,15 +165,15 @@ public class RookShould Vector2 start = new(0, 0); Vector2 end = new(0, 5); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeFalse(); - steps.Should().HaveCount(5); - steps.Should().Contain(new Vector2(0, 1)); - steps.Should().Contain(new Vector2(0, 2)); - steps.Should().Contain(new Vector2(0, 3)); - steps.Should().Contain(new Vector2(0, 4)); - steps.Should().Contain(new Vector2(0, 5)); + Assert.False(rookPlayer1.IsPromoted); + Assert.Equal(5, steps.Count); + Assert.Contains(new Vector2(0, 1), steps); + Assert.Contains(new Vector2(0, 2), steps); + Assert.Contains(new Vector2(0, 3), steps); + Assert.Contains(new Vector2(0, 4), steps); + Assert.Contains(new Vector2(0, 5), steps); } [Fact] @@ -182,10 +182,10 @@ public class RookShould Vector2 start = new(0, 0); Vector2 end = new(1, 1); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeFalse(); - steps.Should().BeEmpty(); + Assert.False(rookPlayer1.IsPromoted); + Assert.Empty(steps); } [Fact] @@ -195,15 +195,15 @@ public class RookShould Vector2 end = new(0, 5); rookPlayer1.Promote(); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeTrue(); - steps.Should().HaveCount(5); - steps.Should().Contain(new Vector2(0, 1)); - steps.Should().Contain(new Vector2(0, 2)); - steps.Should().Contain(new Vector2(0, 3)); - steps.Should().Contain(new Vector2(0, 4)); - steps.Should().Contain(new Vector2(0, 5)); + Assert.True(rookPlayer1.IsPromoted); + Assert.Equal(5, steps.Count); + Assert.Contains(new Vector2(0, 1), steps); + Assert.Contains(new Vector2(0, 2), steps); + Assert.Contains(new Vector2(0, 3), steps); + Assert.Contains(new Vector2(0, 4), steps); + Assert.Contains(new Vector2(0, 5), steps); } [Fact] @@ -213,10 +213,10 @@ public class RookShould Vector2 end = new(1, 1); rookPlayer1.Promote(); - var steps = rookPlayer1.GetPathFromStartToEnd(start, end); + var steps = rookPlayer1.GetPathFromStartToEnd(start, end).ToList(); - rookPlayer1.IsPromoted.Should().BeTrue(); - steps.Should().HaveCount(1); - steps.Should().Contain(new Vector2(1, 1)); + Assert.True(rookPlayer1.IsPromoted); + Assert.Single(steps); + Assert.Contains(new Vector2(1, 1), steps); } } diff --git a/Tests/UnitTests/ShogiBoardStateShould.cs b/Tests/UnitTests/ShogiBoardStateShould.cs index a4488e8..9541e75 100644 --- a/Tests/UnitTests/ShogiBoardStateShould.cs +++ b/Tests/UnitTests/ShogiBoardStateShould.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.ValueObjects; +using Shogi.Domains.ValueObjects; namespace UnitTests; @@ -11,174 +11,174 @@ 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); + Assert.Equal(WhichPiece.Lance, board["A1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["A1"]!.Owner); + Assert.False(board["A1"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["B1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B1"]!.Owner); + Assert.False(board["B1"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["C1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["C1"]!.Owner); + Assert.False(board["C1"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["D1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["D1"]!.Owner); + Assert.False(board["D1"]!.IsPromoted); + Assert.Equal(WhichPiece.King, board["E1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["E1"]!.Owner); + Assert.False(board["E1"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["F1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["F1"]!.Owner); + Assert.False(board["F1"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["G1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["G1"]!.Owner); + Assert.False(board["G1"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["H1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H1"]!.Owner); + Assert.False(board["H1"]!.IsPromoted); + Assert.Equal(WhichPiece.Lance, board["I1"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["I1"]!.Owner); + Assert.False(board["I1"]!.IsPromoted); - 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(); + Assert.Null(board["A2"]); + Assert.Equal(WhichPiece.Bishop, board["B2"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B2"]!.Owner); + Assert.False(board["B2"]!.IsPromoted); + Assert.Null(board["C2"]); + Assert.Null(board["D2"]); + Assert.Null(board["E2"]); + Assert.Null(board["F2"]); + Assert.Null(board["G2"]); + Assert.Equal(WhichPiece.Rook, board["H2"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H2"]!.Owner); + Assert.False(board["H2"]!.IsPromoted); + Assert.Null(board["I2"]); - 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); + Assert.Equal(WhichPiece.Pawn, board["A3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["A3"]!.Owner); + Assert.False(board["A3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["B3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["B3"]!.Owner); + Assert.False(board["B3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["C3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["C3"]!.Owner); + Assert.False(board["C3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["D3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["D3"]!.Owner); + Assert.False(board["D3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["E3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["E3"]!.Owner); + Assert.False(board["E3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["F3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["F3"]!.Owner); + Assert.False(board["F3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["G3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["G3"]!.Owner); + Assert.False(board["G3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["H3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["H3"]!.Owner); + Assert.False(board["H3"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["I3"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["I3"]!.Owner); + Assert.False(board["I3"]!.IsPromoted); - 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(); + Assert.Null(board["A4"]); + Assert.Null(board["B4"]); + Assert.Null(board["C4"]); + Assert.Null(board["D4"]); + Assert.Null(board["E4"]); + Assert.Null(board["F4"]); + Assert.Null(board["G4"]); + Assert.Null(board["H4"]); + Assert.Null(board["I4"]); - 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(); + Assert.Null(board["A5"]); + Assert.Null(board["B5"]); + Assert.Null(board["C5"]); + Assert.Null(board["D5"]); + Assert.Null(board["E5"]); + Assert.Null(board["F5"]); + Assert.Null(board["G5"]); + Assert.Null(board["H5"]); + Assert.Null(board["I5"]); - 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(); + Assert.Null(board["A6"]); + Assert.Null(board["B6"]); + Assert.Null(board["C6"]); + Assert.Null(board["D6"]); + Assert.Null(board["E6"]); + Assert.Null(board["F6"]); + Assert.Null(board["G6"]); + Assert.Null(board["H6"]); + Assert.Null(board["I6"]); - 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); + Assert.Equal(WhichPiece.Pawn, board["A7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["A7"]!.Owner); + Assert.False(board["A7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["B7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B7"]!.Owner); + Assert.False(board["B7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["C7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["C7"]!.Owner); + Assert.False(board["C7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["D7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["D7"]!.Owner); + Assert.False(board["D7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["E7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["E7"]!.Owner); + Assert.False(board["E7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["F7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["F7"]!.Owner); + Assert.False(board["F7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["G7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["G7"]!.Owner); + Assert.False(board["G7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["H7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H7"]!.Owner); + Assert.False(board["H7"]!.IsPromoted); + Assert.Equal(WhichPiece.Pawn, board["I7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["I7"]!.Owner); + Assert.False(board["I7"]!.IsPromoted); - 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(); + Assert.Null(board["A8"]); + Assert.Equal(WhichPiece.Rook, board["B8"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B8"]!.Owner); + Assert.False(board["B8"]!.IsPromoted); + Assert.Null(board["C8"]); + Assert.Null(board["D8"]); + Assert.Null(board["E8"]); + Assert.Null(board["F8"]); + Assert.Null(board["G8"]); + Assert.Equal(WhichPiece.Bishop, board["H8"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H8"]!.Owner); + Assert.False(board["H8"]!.IsPromoted); + Assert.Null(board["I8"]); - 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); + Assert.Equal(WhichPiece.Lance, board["A9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["A9"]!.Owner); + Assert.False(board["A9"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["B9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["B9"]!.Owner); + Assert.False(board["B9"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["C9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["C9"]!.Owner); + Assert.False(board["C9"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["D9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["D9"]!.Owner); + Assert.False(board["D9"]!.IsPromoted); + Assert.Equal(WhichPiece.King, board["E9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["E9"]!.Owner); + Assert.False(board["E9"]!.IsPromoted); + Assert.Equal(WhichPiece.GoldGeneral, board["F9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["F9"]!.Owner); + Assert.False(board["F9"]!.IsPromoted); + Assert.Equal(WhichPiece.SilverGeneral, board["G9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["G9"]!.Owner); + Assert.False(board["G9"]!.IsPromoted); + Assert.Equal(WhichPiece.Knight, board["H9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["H9"]!.Owner); + Assert.False(board["H9"]!.IsPromoted); + Assert.Equal(WhichPiece.Lance, board["I9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, board["I9"]!.Owner); + Assert.False(board["I9"]!.IsPromoted); } } diff --git a/Tests/UnitTests/ShogiShould.cs b/Tests/UnitTests/ShogiShould.cs index f35ee88..1fd21de 100644 --- a/Tests/UnitTests/ShogiShould.cs +++ b/Tests/UnitTests/ShogiShould.cs @@ -1,15 +1,9 @@ -using Shogi.Domain.ValueObjects; +using Shogi.Domains.ValueObjects; namespace UnitTests; public class ShogiShould { - private readonly ITestOutputHelper console; - public ShogiShould(ITestOutputHelper console) - { - this.console = console; - } - [Fact] public void MoveAPieceToAnEmptyPosition() { @@ -17,16 +11,16 @@ public class ShogiShould var shogi = MockShogiBoard(); var board = shogi.BoardState; - board["A4"].Should().BeNull(); + Assert.Null(board["A4"]); var expectedPiece = board["A3"]; - expectedPiece.Should().NotBeNull(); + Assert.NotNull(expectedPiece); // Act shogi.Move("A3", "A4", false); // Assert - board["A3"].Should().BeNull(); - board["A4"].Should().Be(expectedPiece); + Assert.Null(board["A3"]); + Assert.Equal(expectedPiece, board["A4"]); } [Fact] @@ -41,17 +35,14 @@ public class ShogiShould shogi.Move("G7", "G6", false); // P1 Bishop puts P2 in check shogi.Move("B2", "G7", false); - board.InCheck.Should().Be(WhichPlayer.Player2); + Assert.Equal(WhichPlayer.Player2, board.InCheck); // Act - P2 is able to un-check theirself. /// P2 King moves out of check shogi.Move("E9", "E8", false); // Assert - using (new AssertionScope()) - { - board.InCheck.Should().BeNull(); - } + Assert.Null(board.InCheck); } [Fact] @@ -60,18 +51,18 @@ public class ShogiShould // Arrange var shogi = MockShogiBoard(); var board = shogi.BoardState; - board["D5"].Should().BeNull(); + Assert.Null(board["D5"]); // Act var moveResult = shogi.Move("D5", "D6", false); // Assert - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["D5"].Should().BeNull(); - board["D6"].Should().BeNull(); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(board["D5"]); + Assert.Null(board["D6"]); + Assert.Empty(board.Player1Hand); + Assert.Empty(board.Player2Hand); } [Fact] @@ -86,13 +77,11 @@ public class ShogiShould var moveResult = shogi.Move("A3", "A3", false); // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A3"].Should().Be(expectedPiece); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(expectedPiece, board["A3"]); + Assert.Empty(board.Player1Hand); + Assert.Empty(board.Player2Hand); } [Fact] @@ -102,21 +91,18 @@ public class ShogiShould var shogi = MockShogiBoard(); var board = shogi.BoardState; var expectedPiece = board["D1"]; - expectedPiece!.WhichPiece.Should().Be(WhichPiece.GoldGeneral); + Assert.Equal(WhichPiece.GoldGeneral, expectedPiece!.WhichPiece); // Act - Move General illegally var moveResult = shogi.Move("D1", "D5", false); // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["D1"].Should().Be(expectedPiece); - board["D5"].Should().BeNull(); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(expectedPiece, board["D1"]); + Assert.Null(board["D5"]); + Assert.Empty(board.Player1Hand); + Assert.Empty(board.Player2Hand); } [Fact] @@ -126,19 +112,17 @@ public class ShogiShould var shogi = MockShogiBoard(); var board = shogi.BoardState; var expectedPiece = board["A7"]; - expectedPiece!.Owner.Should().Be(WhichPlayer.Player2); - board.WhoseTurn.Should().Be(WhichPlayer.Player1); + Assert.Equal(WhichPlayer.Player2, expectedPiece!.Owner); + Assert.Equal(WhichPlayer.Player1, board.WhoseTurn); // Act - Move Player2 Pawn when it is Player1 turn. var moveResult = shogi.Move("A7", "A6", false); // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A7"].Should().Be(expectedPiece); - board["A6"].Should().BeNull(); - } + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(expectedPiece, board["A7"]); + Assert.Null(board["A6"]); } [Fact] @@ -149,19 +133,17 @@ public class ShogiShould var board = shogi.BoardState; var lance = board["A1"]; var pawn = board["A3"]; - lance!.Owner.Should().Be(pawn!.Owner); + Assert.Equal(lance!.Owner, pawn!.Owner); // Act - Move P1 Lance through P1 Pawn. var moveResult = shogi.Move("A1", "A5", false); // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A1"].Should().Be(lance); - board["A3"].Should().Be(pawn); - board["A5"].Should().BeNull(); - } + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(lance, board["A1"]); + Assert.Equal(pawn, board["A3"]); + Assert.Null(board["A5"]); } [Fact] @@ -172,20 +154,18 @@ public class ShogiShould var board = shogi.BoardState; var knight = board["B1"]; var pawn = board["C3"]; - knight!.Owner.Should().Be(pawn!.Owner); + Assert.Equal(knight!.Owner, pawn!.Owner); // Act - P1 Knight tries to capture P1 Pawn. var moveResult = shogi.Move("B1", "C3", false); - // Arrange - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["B1"].Should().Be(knight); - board["C3"].Should().Be(pawn); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } + // Assert + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(knight, board["B1"]); + Assert.Equal(pawn, board["C3"]); + Assert.Empty(board.Player1Hand); + Assert.Empty(board.Player2Hand); } [Fact] @@ -200,20 +180,18 @@ public class ShogiShould shogi.Move("G7", "G6", false); // P1 Bishop puts P2 in check shogi.Move("B2", "G7", false); - board.InCheck.Should().Be(WhichPlayer.Player2); + Assert.Equal(WhichPlayer.Player2, board.InCheck); var lance = board["I9"]; // Act - P2 moves Lance while in check. var moveResult = shogi.Move("I9", "I8", false); // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board.InCheck.Should().Be(WhichPlayer.Player2); - board["I9"].Should().Be(lance); - board["I8"].Should().BeNull(); - } + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Equal(WhichPlayer.Player2, board.InCheck); + Assert.Equal(lance, board["I9"]); + Assert.Null(board["I8"]); } [Fact] @@ -242,44 +220,44 @@ public class ShogiShould shogi.Move("H9", "I9", false); // P2 Pawn captures P1 Pawn shogi.Move("I4", "I3", false); - board.Player1Hand.Count.Should().Be(4); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - board.WhoseTurn.Should().Be(WhichPlayer.Player1); + Assert.Equal(4, board.Player1Hand.Count); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Knight); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Lance); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Pawn); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Bishop); + Assert.Equal(WhichPlayer.Player1, board.WhoseTurn); // Act | Assert - Illegally placing Knight from the hand in farthest rank. - board["H9"].Should().BeNull(); + Assert.Null(board["H9"]); var moveResult = shogi.Move(WhichPiece.Knight, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(board["H9"]); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Knight); // Act | Assert - Illegally placing Knight from the hand in second farthest row. - board["H8"].Should().BeNull(); + Assert.Null(board["H8"]); moveResult = shogi.Move(WhichPiece.Knight, "H8"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H8"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(board["H8"]); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Knight); // Act | Assert - Illegally place Lance from the hand. - board["H9"].Should().BeNull(); + Assert.Null(board["H9"]); moveResult = shogi.Move(WhichPiece.Knight, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(board["H9"]); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Lance); // Act | Assert - Illegally place Pawn from the hand. - board["H9"].Should().BeNull(); + Assert.Null(board["H9"]); moveResult = shogi.Move(WhichPiece.Pawn, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(board["H9"]); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Pawn); // // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn. // // TODO @@ -291,33 +269,32 @@ public class ShogiShould // Arrange var shogi = MockShogiBoard(); // P1 Pawn - shogi.Move("C3", "C4", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("C3", "C4", false).IsSuccess); // P2 Pawn - shogi.Move("G7", "G6", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("G7", "G6", false).IsSuccess); // P1 Pawn, arbitrary move. - shogi.Move("A3", "A4", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("A3", "A4", false).IsSuccess); // P2 Bishop takes P1 Bishop - shogi.Move("H8", "B2", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("H8", "B2", false).IsSuccess); // P1 Silver takes P2 Bishop - shogi.Move("C1", "B2", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("C1", "B2", false).IsSuccess); // P2 Pawn, arbtrary move - shogi.Move("A7", "A6", false).IsSuccess.Should().BeTrue(); + Assert.True(shogi.Move("A7", "A6", false).IsSuccess); // P1 drop Bishop, place P2 in check - shogi.Move(WhichPiece.Bishop, "G7").IsSuccess.Should().BeTrue(); - shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); - shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["E5"].Should().BeNull(); + Assert.True(shogi.Move(WhichPiece.Bishop, "G7").IsSuccess); + Assert.Equal(WhichPlayer.Player2, shogi.BoardState.InCheck); + Assert.Single(shogi.BoardState.Player2Hand, p => p.WhichPiece == WhichPiece.Bishop); + Assert.Null(shogi.BoardState["E5"]); // Act - P2 places a Bishop while in check. var moveResult = shogi.Move(WhichPiece.Bishop, "E5"); // Assert - using var scope = new AssertionScope(); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - shogi.BoardState["E5"].Should().BeNull(); - shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); - shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Null(shogi.BoardState["E5"]); + Assert.Equal(WhichPlayer.Player2, shogi.BoardState.InCheck); + Assert.Single(shogi.BoardState.Player2Hand, p => p.WhichPiece == WhichPiece.Bishop); } [Fact] @@ -333,22 +310,21 @@ public class ShogiShould shogi.Move("B2", "H8", false); // P2 Pawn shogi.Move("G6", "G5", false); - shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["I9"].Should().NotBeNull(); - shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); - shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); + Assert.Single(shogi.BoardState.Player1Hand, p => p.WhichPiece == WhichPiece.Bishop); + Assert.NotNull(shogi.BoardState["I9"]); + Assert.Equal(WhichPiece.Lance, shogi.BoardState["I9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, shogi.BoardState["I9"]!.Owner); // Act - P1 tries to place a piece where an opponent's piece resides. var moveResult = shogi.Move(WhichPiece.Bishop, "I9"); // Assert - using var scope = new AssertionScope(); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["I9"].Should().NotBeNull(); - shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); - shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); + Assert.NotNull(moveResult); + Assert.False(moveResult.IsSuccess); + Assert.Single(shogi.BoardState.Player1Hand, p => p.WhichPiece == WhichPiece.Bishop); + Assert.NotNull(shogi.BoardState["I9"]); + Assert.Equal(WhichPiece.Lance, shogi.BoardState["I9"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player2, shogi.BoardState["I9"]!.Owner); } [Fact] @@ -366,7 +342,7 @@ public class ShogiShould shogi.Move("B2", "G7", false); // Assert - board.InCheck.Should().Be(WhichPlayer.Player2); + Assert.Equal(WhichPlayer.Player2, board.InCheck); } [Fact] @@ -384,14 +360,11 @@ public class ShogiShould shogi.Move("B2", "G7", true); // Assert - using (new AssertionScope()) - { - board["B2"].Should().BeNull(); - board["G7"].Should().NotBeNull(); - board["G7"]!.WhichPiece.Should().Be(WhichPiece.Bishop); - board["G7"]!.Owner.Should().Be(WhichPlayer.Player1); - board["G7"]!.IsPromoted.Should().BeTrue(); - } + Assert.Null(board["B2"]); + Assert.NotNull(board["G7"]); + Assert.Equal(WhichPiece.Bishop, board["G7"]!.WhichPiece); + Assert.Equal(WhichPlayer.Player1, board["G7"]!.Owner); + Assert.True(board["G7"]!.IsPromoted); } [Fact] @@ -401,7 +374,7 @@ public class ShogiShould var shogi = MockShogiBoard(); var board = shogi.BoardState; var p1Bishop = board["B2"]; - p1Bishop!.WhichPiece.Should().Be(WhichPiece.Bishop); + Assert.Equal(WhichPiece.Bishop, p1Bishop!.WhichPiece); shogi.Move("C3", "C4", false); shogi.Move("G7", "G6", false); @@ -409,13 +382,9 @@ public class ShogiShould shogi.Move("B2", "H8", false); // Assert - board["B2"].Should().BeNull(); - board["H8"].Should().Be(p1Bishop); - - board - .Player1Hand - .Should() - .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPlayer.Player1); + Assert.Null(board["B2"]); + Assert.Equal(p1Bishop, board["H8"]); + Assert.Single(board.Player1Hand, p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPlayer.Player1); } [Fact] @@ -449,8 +418,8 @@ public class ShogiShould shogi.Move("E7", "E8", false); // Assert - checkmate - board.IsCheckmate.Should().BeTrue(); - board.InCheck.Should().Be(WhichPlayer.Player2); + Assert.True(board.IsCheckmate); + Assert.Equal(WhichPlayer.Player2, board.InCheck); } private static ShogiBoard MockShogiBoard() => new(BoardState.StandardStarting); diff --git a/Tests/UnitTests/UnitTests.csproj b/Tests/UnitTests/UnitTests.csproj index 639f4c4..121b2e1 100644 --- a/Tests/UnitTests/UnitTests.csproj +++ b/Tests/UnitTests/UnitTests.csproj @@ -8,29 +8,24 @@ - - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + - - + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cbef4a5..15a0c90 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,7 +13,7 @@ variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' - apiProjectName: 'Shogi.Api' + apiProjectName: 'Shogi' uiProjectName: 'Shogi.UI' steps: @@ -77,5 +77,5 @@ inputs: inputs: sshEndpoint: 'LucaServer' runOptions: 'commands' - commands: 'sudo systemctl restart kestrel-shogi.api.service' + commands: 'sudo systemctl restart kestrel-Shogi.service' readyTimeout: '20000' \ No newline at end of file