From 3bf9aa3ee3b7593b2ad95ecef87c0b8cb5f4fe47 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Wed, 1 Feb 2023 22:49:28 -0600 Subject: [PATCH] Move from the hand. --- Shogi.Api/Controllers/SessionsController.cs | 7 +- Shogi.Api/Managers/SocketConnectionManager.cs | 8 +- Shogi.Api/Repositories/QueryRepository.cs | 25 ++++++ Shogi.Api/Repositories/SessionRepository.cs | 11 ++- Shogi.Api/Services/SocketService.cs | 2 +- .../{ISocketResponse.cs => ISocketMessage.cs} | 4 +- Shogi.Contracts/Socket/ISocketRequest.cs | 9 -- .../Socket/PlayerHasMovedMessage.cs | 2 +- .../Socket/SessionCreatedSocketMessage.cs | 2 +- .../SessionJoinedByPlayerSocketMessage.cs | 4 +- Shogi.Contracts/Types/Session.cs | 4 +- Shogi.Contracts/Types/User.cs | 22 +++-- .../Session/Stored Procedures/CreateMove.sql | 8 +- .../Stored Procedures/ReadUsersBySession.sql | 13 +++ Shogi.Database/Session/Tables/Move.sql | 11 ++- Shogi.Database/Shogi.Database.sqlproj | 1 + Shogi.Database/User/Tables/User.sql | 2 +- Shogi.Domain/Aggregates/Session.cs | 50 ++++++----- Shogi.UI/Pages/Home/GameBoard/GameBoard.razor | 6 +- .../GameBoard/GameBoardPresentation.razor | 44 ++++++---- .../GameBoard/GameboardPresentation.razor.css | 31 ++++--- .../Home/GameBoard/SeatedGameBoard.razor | 86 +++++++++++++------ .../Home/GameBoard/SpectatorGameBoard.razor | 2 +- Shogi.UI/Pages/Home/GamePiece.razor | 2 +- Shogi.UI/Pages/Home/GamePiece.razor.css | 4 + Shogi.UI/Program.cs | 2 - Shogi.UI/Shared/Modal/Modals.razor | 1 - Shogi.UI/Shared/ShogiSocket.cs | 10 +-- Tests/AcceptanceTests/AcceptanceTests.cs | 8 +- 29 files changed, 248 insertions(+), 133 deletions(-) rename Shogi.Contracts/Socket/{ISocketResponse.cs => ISocketMessage.cs} (65%) delete mode 100644 Shogi.Contracts/Socket/ISocketRequest.cs create mode 100644 Shogi.Database/Session/Stored Procedures/ReadUsersBySession.sql diff --git a/Shogi.Api/Controllers/SessionsController.cs b/Shogi.Api/Controllers/SessionsController.cs index 9e72c63..38842a7 100644 --- a/Shogi.Api/Controllers/SessionsController.cs +++ b/Shogi.Api/Controllers/SessionsController.cs @@ -88,6 +88,9 @@ public class SessionsController : ControllerBase var session = await sessionRepository.ReadSession(name); if (session == null) return this.NotFound(); + var players = await queryRespository.GetUsersForSession(session.Name); + if (players == null) return this.NotFound(); + return new ReadSessionResponse { Session = new Session @@ -100,8 +103,8 @@ public class SessionsController : ControllerBase PlayerInCheck = session.Board.BoardState.InCheck?.ToContract(), WhoseTurn = session.Board.BoardState.WhoseTurn.ToContract() }, - Player1 = session.Player1, - Player2 = session.Player2, + Player1 = players.Value.Player1, + Player2 = players.Value.Player2, SessionName = session.Name } }; diff --git a/Shogi.Api/Managers/SocketConnectionManager.cs b/Shogi.Api/Managers/SocketConnectionManager.cs index 8b8709a..6cde2e2 100644 --- a/Shogi.Api/Managers/SocketConnectionManager.cs +++ b/Shogi.Api/Managers/SocketConnectionManager.cs @@ -8,10 +8,10 @@ namespace Shogi.Api.Managers; public interface ISocketConnectionManager { - Task BroadcastToAll(ISocketResponse response); + Task BroadcastToAll(ISocketMessage response); void Subscribe(WebSocket socket, string playerName); void Unsubscribe(string playerName); - Task BroadcastToPlayers(ISocketResponse response, params string?[] playerNames); + Task BroadcastToPlayers(ISocketMessage response, params string?[] playerNames); } /// @@ -45,7 +45,7 @@ public class SocketConnectionManager : ISocketConnectionManager connections.TryRemove(playerName, out _); } - public async Task BroadcastToPlayers(ISocketResponse response, params string?[] playerNames) + public async Task BroadcastToPlayers(ISocketMessage response, params string?[] playerNames) { var tasks = new List(playerNames.Length); foreach (var name in playerNames) @@ -59,7 +59,7 @@ public class SocketConnectionManager : ISocketConnectionManager } await Task.WhenAll(tasks); } - public Task BroadcastToAll(ISocketResponse response) + public Task BroadcastToAll(ISocketMessage response) { var message = Serialize(response); logger.LogInformation("Broadcasting:\n{0}\nDone Broadcasting.", message); diff --git a/Shogi.Api/Repositories/QueryRepository.cs b/Shogi.Api/Repositories/QueryRepository.cs index 1ba6240..89cc942 100644 --- a/Shogi.Api/Repositories/QueryRepository.cs +++ b/Shogi.Api/Repositories/QueryRepository.cs @@ -1,6 +1,7 @@ using Dapper; using Shogi.Contracts.Api; using Shogi.Contracts.Types; +using System.Data; using System.Data.SqlClient; namespace Shogi.Api.Repositories; @@ -32,9 +33,33 @@ public class QueryRepository : IQueryRespository AllOtherSessions = otherSessions.ToList() }; } + + /// + /// + /// A with Item1 as player 1 and Item2 as player 2. + public async Task<(User Player1, User? Player2)?> GetUsersForSession(string sessionName) + { + using var connection = new SqlConnection(connectionString); + var results = await connection.QueryAsync<(string Player1Name, string Player1DisplayName, string Player2Name, string Player2DisplayName)>( + "session.ReadUsersBySession", + new { SessionName = sessionName }, + commandType: CommandType.StoredProcedure); + + if (results.Any()) + { + var (Player1Name, Player1DisplayName, Player2Name, Player2DisplayName) = results.First(); + var p1 = new User(Player1Name, Player1DisplayName); + var p2 = Player2Name != null + ? new User(Player2Name, Player2DisplayName) + : null; + return (p1, p2); + } + return null; + } } public interface IQueryRespository { + Task<(User Player1, User? Player2)?> GetUsersForSession(string sessionName); Task ReadSessionPlayerCount(string playerName); } \ No newline at end of file diff --git a/Shogi.Api/Repositories/SessionRepository.cs b/Shogi.Api/Repositories/SessionRepository.cs index a530dcf..3bdc0ae 100644 --- a/Shogi.Api/Repositories/SessionRepository.cs +++ b/Shogi.Api/Repositories/SessionRepository.cs @@ -73,6 +73,15 @@ public class SessionRepository : ISessionRepository public async Task CreateMove(string sessionName, MovePieceCommand command) { + var yep = new + { + command.To, + command.From, + command.IsPromotion, + command.PieceFromHand, + SessionName = sessionName + }; + using var connection = new SqlConnection(connectionString); await connection.ExecuteAsync( "session.CreateMove", @@ -81,7 +90,7 @@ public class SessionRepository : ISessionRepository command.To, command.From, command.IsPromotion, - command.PieceFromHand, + PieceFromHand = command.PieceFromHand.ToString(), SessionName = sessionName }, commandType: CommandType.StoredProcedure); diff --git a/Shogi.Api/Services/SocketService.cs b/Shogi.Api/Services/SocketService.cs index c3bf257..3921202 100644 --- a/Shogi.Api/Services/SocketService.cs +++ b/Shogi.Api/Services/SocketService.cs @@ -59,7 +59,7 @@ namespace Shogi.Api.Services var message = await socket.ReceiveTextAsync(); if (string.IsNullOrWhiteSpace(message)) continue; logger.LogInformation("Request \n{0}\n", message); - var request = JsonSerializer.Deserialize(message); + var request = JsonSerializer.Deserialize(message); if (request == null || !Enum.IsDefined(typeof(SocketAction), request.Action)) { await socket.SendTextAsync("Error: Action not recognized."); diff --git a/Shogi.Contracts/Socket/ISocketResponse.cs b/Shogi.Contracts/Socket/ISocketMessage.cs similarity index 65% rename from Shogi.Contracts/Socket/ISocketResponse.cs rename to Shogi.Contracts/Socket/ISocketMessage.cs index 0e3b95d..675d1fe 100644 --- a/Shogi.Contracts/Socket/ISocketResponse.cs +++ b/Shogi.Contracts/Socket/ISocketMessage.cs @@ -2,12 +2,12 @@ namespace Shogi.Contracts.Socket; -public interface ISocketResponse +public interface ISocketMessage { SocketAction Action { get; } } -public class SocketResponse : ISocketResponse +public class SocketResponse : ISocketMessage { public SocketAction Action { get; set; } } diff --git a/Shogi.Contracts/Socket/ISocketRequest.cs b/Shogi.Contracts/Socket/ISocketRequest.cs deleted file mode 100644 index 8d5c4db..0000000 --- a/Shogi.Contracts/Socket/ISocketRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Shogi.Contracts.Types; - -namespace Shogi.Contracts.Socket -{ - public interface ISocketRequest - { - SocketAction Action { get; } - } -} diff --git a/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs b/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs index 4309246..048ad9e 100644 --- a/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs +++ b/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs @@ -2,7 +2,7 @@ namespace Shogi.Contracts.Socket; -public class PlayerHasMovedMessage : ISocketResponse +public class PlayerHasMovedMessage : ISocketMessage { public SocketAction Action { get; } public string SessionName { get; set; } diff --git a/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs b/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs index 0dd8b3f..4700a9d 100644 --- a/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs +++ b/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs @@ -2,7 +2,7 @@ namespace Shogi.Contracts.Socket; -public class SessionCreatedSocketMessage : ISocketResponse +public class SessionCreatedSocketMessage : ISocketMessage { public SocketAction Action => SocketAction.SessionCreated; } diff --git a/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs b/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs index d6c8f53..0457226 100644 --- a/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs +++ b/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs @@ -2,11 +2,11 @@ namespace Shogi.Contracts.Socket; -public class SessionJoinedByPlayerSocketMessage : ISocketResponse +public class SessionJoinedByPlayerSocketMessage : ISocketMessage { public SocketAction Action => SocketAction.SessionJoined; - public string SessionName { get; } + public string SessionName { get; set; } public SessionJoinedByPlayerSocketMessage(string sessionName) { diff --git a/Shogi.Contracts/Types/Session.cs b/Shogi.Contracts/Types/Session.cs index ecd4f16..a6676aa 100644 --- a/Shogi.Contracts/Types/Session.cs +++ b/Shogi.Contracts/Types/Session.cs @@ -2,8 +2,8 @@ public class Session { - public string Player1 { get; set; } - public string? Player2 { get; set; } + public User Player1 { get; set; } + public User? Player2 { get; set; } public string SessionName { get; set; } public BoardState BoardState { get; set; } } diff --git a/Shogi.Contracts/Types/User.cs b/Shogi.Contracts/Types/User.cs index 610bfa9..2cfc70b 100644 --- a/Shogi.Contracts/Types/User.cs +++ b/Shogi.Contracts/Types/User.cs @@ -1,9 +1,17 @@ -namespace Shogi.Contracts.Types -{ - public class User - { - public string Id { get; set; } = string.Empty; +namespace Shogi.Contracts.Types; - public string Name { get; set; } = string.Empty; - } +public class User +{ + public string Id { get; set; } = string.Empty; + + /// + /// A display name for the user. + /// + public string Name { get; set; } = string.Empty; + + public User(string id, string name) + { + Id = id; + Name = name; + } } diff --git a/Shogi.Database/Session/Stored Procedures/CreateMove.sql b/Shogi.Database/Session/Stored Procedures/CreateMove.sql index ac4b184..6125e59 100644 --- a/Shogi.Database/Session/Stored Procedures/CreateMove.sql +++ b/Shogi.Database/Session/Stored Procedures/CreateMove.sql @@ -1,8 +1,8 @@ CREATE PROCEDURE [session].[CreateMove] @To VARCHAR(2), - @From VARCHAR(2), - @IsPromotion BIT, - @PieceFromHand NVARCHAR(13), + @From VARCHAR(2) = NULL, + @IsPromotion BIT = 0, + @PieceFromHand NVARCHAR(13) = NULL, @SessionName [session].[SessionName] AS @@ -19,7 +19,7 @@ BEGIN WHERE [Name] = @SessionName; DECLARE @PieceIdFromhand INT = NULL; - SELECT @PieceIdFromhand + SELECT @PieceIdFromhand = Id FROM [session].[Piece] WHERE [Name] = @PieceFromHand; diff --git a/Shogi.Database/Session/Stored Procedures/ReadUsersBySession.sql b/Shogi.Database/Session/Stored Procedures/ReadUsersBySession.sql new file mode 100644 index 0000000..d8c6ed7 --- /dev/null +++ b/Shogi.Database/Session/Stored Procedures/ReadUsersBySession.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [session].[ReadUsersBySession] + @SessionName [session].[SessionName] +AS + +SELECT + p1.[Name] as Player1Name, + p1.DisplayName as Player1DisplayName, + p2.[Name] as Player2Name, + p2.DisplayName as Player2Displayname +FROM [session].[Session] sess + INNER JOIN [user].[User] p1 ON sess.Player1Id = p1.Id + LEFT JOIN [user].[User] p2 on sess.Player2Id = p2.Id +WHERE sess.[Name] = @SessionName; diff --git a/Shogi.Database/Session/Tables/Move.sql b/Shogi.Database/Session/Tables/Move.sql index 4b4b2f3..f5b00f6 100644 --- a/Shogi.Database/Session/Tables/Move.sql +++ b/Shogi.Database/Session/Tables/Move.sql @@ -4,12 +4,19 @@ [SessionId] BIGINT NOT NULL, [To] VARCHAR(2) NOT NULL, [From] VARCHAR(2) NULL, - [IsPromotion] BIT NULL, - [PieceIdFromHand] INT NULL + [PieceIdFromHand] INT NULL, + [IsPromotion] BIT DEFAULT 0 CONSTRAINT [Cannot end where you start] CHECK ([From] <> [To]), + CONSTRAINT [Move cannot start from two places] + CHECK ( + ( [From] IS NOT NULL AND [PieceIdFromHand] IS NULL ) + OR + ( [From] IS NULL AND [PieceIdFromhand] IS NOT NULL ) + ), + CONSTRAINT FK_Move_Session FOREIGN KEY (SessionId) REFERENCES [session].[Session] (Id) ON DELETE CASCADE ON UPDATE CASCADE, diff --git a/Shogi.Database/Shogi.Database.sqlproj b/Shogi.Database/Shogi.Database.sqlproj index 51ec8c5..e4b8681 100644 --- a/Shogi.Database/Shogi.Database.sqlproj +++ b/Shogi.Database/Shogi.Database.sqlproj @@ -88,6 +88,7 @@ + diff --git a/Shogi.Database/User/Tables/User.sql b/Shogi.Database/User/Tables/User.sql index b0b856d..f0915f9 100644 --- a/Shogi.Database/User/Tables/User.sql +++ b/Shogi.Database/User/Tables/User.sql @@ -1,6 +1,6 @@ CREATE TABLE [user].[User] ( - [Id] BIGINT NOT NULL PRIMARY KEY IDENTITY, + [Id] BIGINT NOT NULL PRIMARY KEY IDENTITY, -- TODO: Consider using user.UserName as the PK to avoid confusing "Id" in the database vs "Id" in the domain model. [Name] [user].[UserName] NOT NULL UNIQUE, [DisplayName] NVARCHAR(100) NOT NULL, [Platform] NVARCHAR(20) NOT NULL, diff --git a/Shogi.Domain/Aggregates/Session.cs b/Shogi.Domain/Aggregates/Session.cs index 4ae8e02..a48bca2 100644 --- a/Shogi.Domain/Aggregates/Session.cs +++ b/Shogi.Domain/Aggregates/Session.cs @@ -4,29 +4,35 @@ namespace Shogi.Domain; public class Session { - public Session( - string name, - string player1Name) - { - Name = name; - Player1 = player1Name; - Board = new(BoardState.StandardStarting); - } + public Session( + string name, + string player1Name) + { + Name = name; + Player1 = player1Name; + Board = new(BoardState.StandardStarting); + } - public string Name { get; } - public ShogiBoard Board { get; } - public string Player1 { get; } - public string? Player2 { get; private set; } + public string Name { get; } + public ShogiBoard Board { get; } + /// + /// The User.Id of the player which created the session. + /// + public string Player1 { get; } + /// + /// The User.Id of the second player. + /// + public string? Player2 { get; private set; } - public void AddPlayer2(string player2Name) - { - if (Player2 != null) throw new InvalidOperationException("Player 2 already exists while trying to add a second player."); - if (Player1.Equals(player2Name, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("Player 2 must be different from Player 1"); - Player2 = player2Name; - } + public void AddPlayer2(string player2Name) + { + if (Player2 != null) throw new InvalidOperationException("Player 2 already exists while trying to add a second player."); + if (Player1.Equals(player2Name, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("Player 2 must be different from Player 1"); + Player2 = player2Name; + } - public bool IsSeated(string playerName) - { - return Player1 == playerName || Player2 == playerName; - } + public bool IsSeated(string playerName) + { + return Player1 == playerName || Player2 == playerName; + } } diff --git a/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor b/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor index d7e316e..5167faa 100644 --- a/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor @@ -59,9 +59,9 @@ else { var accountId = Account.User?.Id; - this.perspective = accountId == session.Player2 ? WhichPlayer.Player2 : WhichPlayer.Player1; - this.isSpectating = !(accountId == this.session.Player1 || accountId == this.session.Player2); - Console.WriteLine($"IsSpectating - {isSpectating}. AccountId - {accountId}. Player1 - {this.session.Player1}. Player2 - {this.session.Player2}"); + this.perspective = accountId == session.Player1.Id ? WhichPlayer.Player1 : WhichPlayer.Player2; + Console.WriteLine(new { this.perspective, accountId }); + this.isSpectating = !(accountId == this.session.Player1.Id || accountId == this.session.Player2?.Id); } StateHasChanged(); diff --git a/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor b/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor index 6759e45..93fcac0 100644 --- a/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor +++ b/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor @@ -1,4 +1,5 @@ @using Shogi.Contracts.Types; +@using System.Text.Json; @inject PromotePrompt PromotePrompt; @inject AccountState AccountState; @@ -70,7 +71,6 @@
@if (opponentHand.Any()) { - @foreach (var piece in opponentHand) {
@@ -78,20 +78,29 @@
} } - else - { - Hand is empty. - }
+

Opponent Hand

-
+

@opponentName

+

+ + @if (IsMyTurn) + { + + } + else + { + + } + +

@userName

- @if (Session.Player2 == null && Session.Player1 != AccountState.User?.Id) + @if (Session.Player2 == null && Session.Player1.Id != AccountState.User?.Id) {

Seat is Empty

@@ -100,20 +109,19 @@ } else { +

Hand

@if (userHand.Any()) { @foreach (var piece in userHand) { -
+
} } - else - { - Hand is empty. - }
}
@@ -132,10 +140,12 @@ [Parameter] public WhichPlayer Perspective { get; set; } [Parameter] public Session? Session { get; set; } [Parameter] public string? SelectedPosition { get; set; } + [Parameter] public WhichPiece? SelectedPieceFromHand { get; set; } // TODO: Exchange these OnClick actions for events like "SelectionChangedEvent" and "MoveFromBoardEvent" and "MoveFromHandEvent". [Parameter] public Func? OnClickTile { get; set; } [Parameter] public Func? OnClickHand { get; set; } [Parameter] public Func? OnClickJoinGame { get; set; } + [Parameter] public bool IsMyTurn { get; set; } private IReadOnlyCollection opponentHand; private IReadOnlyCollection userHand; @@ -152,7 +162,6 @@ protected override void OnParametersSet() { - Console.WriteLine("Params changed. SelectedPosition = {0}", SelectedPosition); base.OnParametersSet(); if (Session == null) { @@ -163,6 +172,7 @@ } else { + Console.WriteLine(JsonSerializer.Serialize(new { this.Session.Player1, this.Session.Player2, Perspective, this.Session.SessionName })); opponentHand = Perspective == WhichPlayer.Player1 ? this.Session.BoardState.Player2Hand : this.Session.BoardState.Player1Hand; @@ -170,11 +180,11 @@ ? this.Session.BoardState.Player1Hand : this.Session.BoardState.Player2Hand; userName = Perspective == WhichPlayer.Player1 - ? this.Session.Player1 - : this.Session.Player2; + ? this.Session.Player1.Name + : this.Session.Player2?.Name ?? "Empty Seat"; opponentName = Perspective == WhichPlayer.Player1 - ? this.Session.Player2 - : this.Session.Player1; + ? this.Session.Player2?.Name ?? "Empty Seat" + : this.Session.Player1.Name; } } diff --git a/Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css b/Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css index dcf4002..94fda80 100644 --- a/Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css +++ b/Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css @@ -1,7 +1,7 @@ .game-board { display: grid; grid-template-areas: "board side-board icons"; - grid-template-columns: 3fr minmax(10rem, 1fr) auto; + grid-template-columns: max-content minmax(25rem, 1fr) 2fr; gap: 0.5rem; background-color: #444; position: relative; /* For absolute positioned children. */ @@ -58,17 +58,18 @@ } .tile { - background-color: beige; display: grid; place-content: center; - padding: 0.25rem; - overflow: hidden; /* Because SVGs are shaped weird */ transition: filter linear 0.25s; } - .tile[data-selected] { - filter: invert(0.8); - } +.board .tile { + background-color: beige; +} + +.tile[data-selected] { + filter: invert(0.8); +} .ruler { color: beige; @@ -90,22 +91,32 @@ } .side-board { - display: grid; - grid-auto-rows: 1fr; + display: flex; + flex-direction: column; + place-content: space-between; padding: 1rem; background-color: var(--contrast-color); } .side-board .player-area { display: grid; + place-items: stretch; } .side-board .hand { display: grid; - grid-auto-columns: 1fr; border: 1px solid #ccc; + grid-template-columns: repeat(7, 1fr); + grid-template-rows: 4rem; + place-items: center start; + padding: 0.5rem; } + .side-board .hand .tile { + max-height: 100%; /* I have no idea why I need to set this here to prevent a height blowout. */ + background-color: var(--secondary-color); + } + .promote-prompt { display: none; position: absolute; diff --git a/Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor b/Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor index 4bb7fd1..9bd3528 100644 --- a/Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor @@ -9,7 +9,9 @@ Perspective="Perspective" OnClickHand="OnClickHand" OnClickTile="OnClickTile" - SelectedPosition="@selectedBoardPosition" /> + SelectedPosition="@selectedBoardPosition" + SelectedPieceFromHand="@selectedPieceFromHand" + IsMyTurn="IsMyTurn" /> @code { [Parameter, EditorRequired] @@ -23,6 +25,8 @@ protected override void OnParametersSet() { base.OnParametersSet(); + selectedBoardPosition = null; + selectedPieceFromHand = null; if (Session == null) { throw new ArgumentException($"{nameof(Session)} cannot be null.", nameof(Session)); @@ -42,51 +46,77 @@ return false; } - async Task OnClickTile(Piece? piece, string position) + async Task OnClickTile(Piece? pieceAtPosition, string position) { - Console.WriteLine("Is my turn?"); - Console.WriteLine(true); if (!IsMyTurn) return; - if (selectedBoardPosition == null || piece?.Owner == Perspective) - { - // Select a position. - Console.WriteLine("Position {0}", position); - selectedBoardPosition = position; - StateHasChanged(); - return; - } + if (selectedBoardPosition == position) { // Deselect the selected position. selectedBoardPosition = null; + StateHasChanged(); return; } - if (piece == null) + + if (selectedBoardPosition == null && pieceAtPosition?.Owner == Perspective) { - if (ShouldPromptForPromotion(position) || ShouldPromptForPromotion(selectedBoardPosition)) + // Select an owned piece. + Console.WriteLine("Selecting piece owned by {0} while I am perspective {1}", pieceAtPosition?.Owner, Perspective); + selectedBoardPosition = position; + // Prevent selecting pieces from the hand and board at the same time. + selectedPieceFromHand = null; + StateHasChanged(); + return; + } + + if (selectedPieceFromHand is not null) + { + if (pieceAtPosition is null) { - PromotePrompt.Show(Session.SessionName, new MovePieceCommand - { - From = selectedBoardPosition, - To = position - }); + Console.WriteLine("Moving piece from hand."); + // Placing a piece from the hand to an empty space. + // await ShogiApi.Move( + // Session.SessionName, + // new MovePieceCommand(selectedPieceFromHand.Value, position)); } - else + StateHasChanged(); + return; + } + + if (selectedBoardPosition != null) + { + if (pieceAtPosition == null || pieceAtPosition?.Owner != Perspective) { - await ShogiApi.Move(Session.SessionName, new MovePieceCommand - { - From = selectedBoardPosition, - IsPromotion = false, - To = position - }); + // Moving to an empty space or capturing an opponent's piece. + if (ShouldPromptForPromotion(position) || ShouldPromptForPromotion(selectedBoardPosition)) + { + PromotePrompt.Show( + Session.SessionName, + new MovePieceCommand(selectedBoardPosition, position, false)); + } + else + { + await ShogiApi.Move(Session.SessionName, new MovePieceCommand(selectedBoardPosition, position, false)); + } + StateHasChanged(); + return; } } } async Task OnClickHand(Piece piece) { - selectedPieceFromHand = piece.WhichPiece; - await Task.CompletedTask; + if (!IsMyTurn) return; + + // Prevent selecting from both the hand and the board. + selectedBoardPosition = null; + + selectedPieceFromHand = piece.WhichPiece == selectedPieceFromHand + // Deselecting the already-selected piece + ? selectedPieceFromHand = null + : selectedPieceFromHand = piece.WhichPiece; + + StateHasChanged(); } } diff --git a/Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor b/Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor index 8a5f386..055295a 100644 --- a/Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor @@ -3,7 +3,7 @@ @inject IShogiApi ShogiApi; diff --git a/Shogi.UI/Pages/Home/GamePiece.razor b/Shogi.UI/Pages/Home/GamePiece.razor index 3f22394..38c6b3d 100644 --- a/Shogi.UI/Pages/Home/GamePiece.razor +++ b/Shogi.UI/Pages/Home/GamePiece.razor @@ -1,6 +1,6 @@ @using Shogi.Contracts.Types -
+
@switch (Piece?.WhichPiece) { case WhichPiece.Bishop: diff --git a/Shogi.UI/Pages/Home/GamePiece.razor.css b/Shogi.UI/Pages/Home/GamePiece.razor.css index 0dae5fc..d9dc99b 100644 --- a/Shogi.UI/Pages/Home/GamePiece.razor.css +++ b/Shogi.UI/Pages/Home/GamePiece.razor.css @@ -6,3 +6,7 @@ [data-upsidedown] { transform: rotateZ(180deg); } + +.game-piece { + overflow: hidden; /* Because SVGs have weird sizes. */ +} \ No newline at end of file diff --git a/Shogi.UI/Program.cs b/Shogi.UI/Program.cs index 6a5d3a2..8a40c1b 100644 --- a/Shogi.UI/Program.cs +++ b/Shogi.UI/Program.cs @@ -63,8 +63,6 @@ static void ConfigureDependencies(IServiceCollection services, IConfiguration co var serializerOptions = new JsonSerializerOptions { - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; services.AddScoped((sp) => serializerOptions); diff --git a/Shogi.UI/Shared/Modal/Modals.razor b/Shogi.UI/Shared/Modal/Modals.razor index 5be1583..f4e8f1a 100644 --- a/Shogi.UI/Shared/Modal/Modals.razor +++ b/Shogi.UI/Shared/Modal/Modals.razor @@ -29,7 +29,6 @@ void OnModalChange(object? sender, ModalVisibilityChangedEventArgs args) { - Console.WriteLine("Modal Change"); if (args != null) { shouldShow = args.LoginModalIsVisible || args.GuestAccountDescriptionIsVisible; diff --git a/Shogi.UI/Shared/ShogiSocket.cs b/Shogi.UI/Shared/ShogiSocket.cs index 0913225..ef42795 100644 --- a/Shogi.UI/Shared/ShogiSocket.cs +++ b/Shogi.UI/Shared/ShogiSocket.cs @@ -38,11 +38,9 @@ public class ShogiSocket : IDisposable }.ToQueryString().Value; await socket.ConnectAsync(this.uriBuilder.Uri, cancelToken.Token); - Console.WriteLine("Socket Connected"); // Fire and forget! I'm way too lazy to write my own javascript interop to a web worker. Nooo thanks. _ = Listen().ContinueWith(async antecedent => { - Console.WriteLine($"Socket fault. {antecedent.Exception}"); this.cancelToken.Cancel(); await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Page was probably closed or refresh.", CancellationToken.None); if (antecedent.Exception != null) @@ -59,9 +57,9 @@ public class ShogiSocket : IDisposable var result = await socket.ReceiveAsync(this.memoryOwner.Memory, cancelToken.Token); var memory = this.memoryOwner.Memory[..result.Count].ToArray(); var action = JsonDocument - .Parse(memory[..result.Count]) + .Parse(memory) .RootElement - .GetProperty(nameof(ISocketResponse.Action)) + .GetProperty(nameof(ISocketMessage.Action)) .Deserialize(); Console.WriteLine($"Socket action: {action}"); @@ -76,14 +74,14 @@ public class ShogiSocket : IDisposable case SocketAction.SessionJoined: if (this.OnSessionJoined is not null) { - var args = JsonSerializer.Deserialize(memory[..result.Count], serializerOptions); + var args = JsonSerializer.Deserialize(memory, serializerOptions); await this.OnSessionJoined(args!); } break; case SocketAction.PieceMoved: if (this.OnPlayerMoved is not null) { - var args = JsonSerializer.Deserialize(memory[..result.Count], serializerOptions); + var args = JsonSerializer.Deserialize(memory, serializerOptions); await this.OnPlayerMoved(args!); } break; diff --git a/Tests/AcceptanceTests/AcceptanceTests.cs b/Tests/AcceptanceTests/AcceptanceTests.cs index 9cc5578..e8acdd8 100644 --- a/Tests/AcceptanceTests/AcceptanceTests.cs +++ b/Tests/AcceptanceTests/AcceptanceTests.cs @@ -37,7 +37,9 @@ public class AcceptanceTests : IClassFixture // Assert joinResponse.StatusCode.Should().Be(HttpStatusCode.OK); var readSessionResponse = await ReadTestSession(); - readSessionResponse.Session.Player2.Should().NotBeNullOrEmpty(); + readSessionResponse.Session.Player2.Should().NotBeNull(); + readSessionResponse.Session.Player2!.Id.Should().NotBeNullOrEmpty();; + readSessionResponse.Session.Player2.Name.Should().NotBeNullOrEmpty(); ; } finally { @@ -55,7 +57,7 @@ public class AcceptanceTests : IClassFixture var joinResponse = await guest2HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Join", UriKind.Relative), null); joinResponse.StatusCode.Should().Be(HttpStatusCode.OK); var readSessionResponse = await ReadTestSession(); - readSessionResponse.Session.Player2.Should().NotBeNullOrEmpty(); + readSessionResponse.Session.Player2.Should().NotBeNull(); // Act joinResponse = await guest2HttpClient.PatchAsync(new Uri("Sessions/Acceptance Tests/Join", UriKind.Relative), null); @@ -119,7 +121,7 @@ public class AcceptanceTests : IClassFixture response.Session.BoardState.Player2Hand.Should().BeEmpty(); response.Session.BoardState.PlayerInCheck.Should().BeNull(); response.Session.BoardState.WhoseTurn.Should().Be(WhichPlayer.Player1); - response.Session.Player1.Should().NotBeNullOrEmpty(); + response.Session.Player1.Should().NotBeNull(); response.Session.Player2.Should().BeNull(); response.Session.SessionName.Should().Be("Acceptance Tests"); }