diff --git a/Shogi.Api/Application/ShogiApplication.cs b/Shogi.Api/Application/ShogiApplication.cs index fc38692..e659b46 100644 --- a/Shogi.Api/Application/ShogiApplication.cs +++ b/Shogi.Api/Application/ShogiApplication.cs @@ -87,28 +87,21 @@ public class ShogiApplication( return new ForbidResult(); } - try + var moveResult = command.PieceFromHand.HasValue + ? session.Board.Move(command.PieceFromHand.Value.ToDomain(), command.To) + : session.Board.Move(command.From!, command.To, command.IsPromotion ?? false); + + if (moveResult.IsSuccess) { - if (command.PieceFromHand.HasValue) - { - session.Board.Move(command.PieceFromHand.Value.ToDomain(), command.To); - } - else - { - var isPromotion = command.IsPromotion ?? false; - session.Board.Move(command.From!, command.To, isPromotion); - } + await sessionRepository.CreateMove(sessionId, command); + await gameHubContext.Emit_PieceMoved(sessionId); + return new NoContentResult(); } - catch (InvalidOperationException e) + else { - return new ConflictObjectResult(e.Message); + return new ConflictObjectResult(moveResult.Reason); } - await sessionRepository.CreateMove(sessionId, command); - - await gameHubContext.Emit_PieceMoved(sessionId); - - return new NoContentResult(); } public async Task JoinSession(string sessionId, string player2Id) diff --git a/Shogi.UI/Pages/Home/HomePage.razor b/Shogi.UI/Pages/Home/HomePage.razor index e1ea6e3..ee01fd5 100644 --- a/Shogi.UI/Pages/Home/HomePage.razor +++ b/Shogi.UI/Pages/Home/HomePage.razor @@ -31,13 +31,11 @@

Capturing and the Hand

The King and "Check"

Victory

- @code { - + private bool show = true; private string activeSessionName = string.Empty; - private Task OnLoginChanged() { StateHasChanged(); @@ -48,4 +46,10 @@ activeSessionName = s.SessionId.ToString(); StateHasChanged(); } + + private void OnClickClose() + { + show = false; + StateHasChanged(); + } } diff --git a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor index 6eb63e8..0b4f2f7 100644 --- a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor +++ b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor @@ -14,8 +14,6 @@ @if (showPromotePrompt) { - -

Do you wish to promote?

@@ -25,10 +23,17 @@
} + @if (showError) + { +
+ +

That is not a valid move.

+
+
+ } + - - -@code { + @code { [Parameter, EditorRequired] public WhichPlayer Perspective { get; set; } [Parameter, EditorRequired] @@ -37,6 +42,7 @@ private WhichPiece? selectedPieceFromHand; private bool showPromotePrompt; private string? moveTo; + private bool showError = false; protected override void OnParametersSet() { @@ -96,6 +102,7 @@ if (!success) { selectedPieceFromHand = null; + showError = true; } } StateHasChanged(); @@ -119,9 +126,12 @@ else { var success = await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, position, false)); + Console.WriteLine("Success? {0}", success); if (!success) { selectedBoardPosition = null; + showError = true; + Console.WriteLine("Show error"); } } StateHasChanged(); @@ -149,11 +159,16 @@ { if (selectedBoardPosition != null && moveTo != null) { - await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, moveTo, shouldPromote)); + showError = await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, moveTo, shouldPromote)); showPromotePrompt = false; return; } throw new InvalidOperationException("Unexpected scenario during OnClickPromotionChoice."); } + + void HideError() + { + showError = false; + } } diff --git a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css index 05a3c33..729c4fc 100644 --- a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css +++ b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor.css @@ -8,7 +8,12 @@ box-shadow: 1px 1px 1px #444; text-align: center; z-index: 101; - background-color: #444; border: 1px solid black; -} \ No newline at end of file +} + +.errorModal { + position: absolute; + top: 1rem; + right: 1rem; +} diff --git a/Shogi.UI/Pages/SearchPage.razor.css b/Shogi.UI/Pages/SearchPage.razor.css index 496d121..f14027d 100644 --- a/Shogi.UI/Pages/SearchPage.razor.css +++ b/Shogi.UI/Pages/SearchPage.razor.css @@ -1,4 +1,3 @@ .SearchPage { - background-color: var(--contrast-color); padding: 0 0.5rem; } diff --git a/Shogi.UI/Shared/TemporaryModal.razor b/Shogi.UI/Shared/TemporaryModal.razor new file mode 100644 index 0000000..76925d1 --- /dev/null +++ b/Shogi.UI/Shared/TemporaryModal.razor @@ -0,0 +1,39 @@ +@using System.Timers +
+
+ @ChildContent +
+ +
+
+
+
+
This message will close soon.
+
+
+ +@code { + [Parameter][EditorRequired] public RenderFragment? ChildContent { get; set; } + [Parameter][EditorRequired] public EventCallback OnClickClose { get; set; } + + [Parameter] public int SecondsUntilClose { get; set; } = 3; + + private string TimeToClose => $"{SecondsUntilClose}s"; + private System.Timers.Timer closingTimer = new System.Timers.Timer(); + + protected override void OnParametersSet() + { + closingTimer = new System.Timers.Timer(TimeSpan.FromSeconds(SecondsUntilClose).TotalMilliseconds) + { + AutoReset = false + }; + closingTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimerElapsed); + + closingTimer.Start(); + } + + private void OnTimerElapsed(object? source, ElapsedEventArgs elapsedEventArgs) + { + OnClickClose.InvokeAsync(); + } +} diff --git a/Shogi.UI/Shared/TemporaryModal.razor.css b/Shogi.UI/Shared/TemporaryModal.razor.css new file mode 100644 index 0000000..67a7a94 --- /dev/null +++ b/Shogi.UI/Shared/TemporaryModal.razor.css @@ -0,0 +1,64 @@ +/* Grid layout */ +.TemporaryModal { + --timeToClose: 3s; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: repeat(1fr, 3) auto; + grid-template-areas: + "content close" + "content ." + "countdown countdown" + "helper-text helper-text"; + gap: 0.5rem; +} + +.content { + grid-area: content; +} + +button.close { + grid-area: close; +} + +.countdown { + grid-area: countdown; +} + + +.time-helper-text { + grid-area: helper-text; +} + +/* Apperance */ + +.TemporaryModal { + padding: 0.5rem; +} + +.countdown .time-background { + background-color: beige; + height: 1ch; +} + +.countdown .time { + background-color: red; + height: 100%; + width: 100%; + animation: countdown var(--timeToClose) ease-in-out; + animation-fill-mode: both; +} + +.countdown .time-helper-text { + font-size: 70%; + text-align: center; +} + +@keyframes countdown { + 0% { + width: 100%; + } + + 100% { + width: 0 + } +}