From 7d47fafea08552f89c3f1ed226f19a5450960ceb Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Fri, 25 Oct 2024 10:30:47 -0500 Subject: [PATCH] yep --- Shogi.Api/Application/ShogiApplication.cs | 7 +- Shogi.Api/Shogi.Api.csproj | 14 ++-- .../ValueObjects/{WhichPiece.cs => Enums.cs} | 15 ++++ Shogi.Domain/ValueObjects/InCheckResult.cs | 9 -- Shogi.Domain/ValueObjects/ShogiBoard.cs | 82 ++++++++++--------- .../GameBoard/GameBoardPresentation.razor | 18 ++-- .../Play/GameBoard/SeatedGameBoard.razor | 31 ++++--- Shogi.UI/Pages/Play/GameBoard/TestCbParams.cs | 7 ++ Shogi.UI/Shogi.UI.csproj | 20 ++--- .../Shogi.AcceptanceTests.csproj | 18 ++-- Tests/UnitTests/UnitTests.csproj | 10 +-- 11 files changed, 124 insertions(+), 107 deletions(-) rename Shogi.Domain/ValueObjects/{WhichPiece.cs => Enums.cs} (50%) delete mode 100644 Shogi.Domain/ValueObjects/InCheckResult.cs create mode 100644 Shogi.UI/Pages/Play/GameBoard/TestCbParams.cs diff --git a/Shogi.Api/Application/ShogiApplication.cs b/Shogi.Api/Application/ShogiApplication.cs index 7e6096e..926f18c 100644 --- a/Shogi.Api/Application/ShogiApplication.cs +++ b/Shogi.Api/Application/ShogiApplication.cs @@ -59,9 +59,9 @@ public class ShogiApplication( { session.Board.Move(move.PieceFromHand.Value, move.To); } - else if (move.From != null) + else if (move.From != null && move.IsPromotion.HasValue) { - session.Board.Move(move.From, move.To, false); + session.Board.Move(move.From, move.To, move.IsPromotion.Value); } else { @@ -95,7 +95,8 @@ public class ShogiApplication( } else { - session.Board.Move(command.From!, command.To, command.IsPromotion ?? false); + var isPromotion = command.IsPromotion.HasValue ? command.IsPromotion.Value : false; + session.Board.Move(command.From!, command.To, isPromotion); } } catch (InvalidOperationException e) diff --git a/Shogi.Api/Shogi.Api.csproj b/Shogi.Api/Shogi.Api.csproj index 2394d04..59214e2 100644 --- a/Shogi.Api/Shogi.Api.csproj +++ b/Shogi.Api/Shogi.Api.csproj @@ -23,16 +23,16 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/Shogi.Domain/ValueObjects/WhichPiece.cs b/Shogi.Domain/ValueObjects/Enums.cs similarity index 50% rename from Shogi.Domain/ValueObjects/WhichPiece.cs rename to Shogi.Domain/ValueObjects/Enums.cs index fab4429..290c143 100644 --- a/Shogi.Domain/ValueObjects/WhichPiece.cs +++ b/Shogi.Domain/ValueObjects/Enums.cs @@ -1,5 +1,20 @@ namespace Shogi.Domain.ValueObjects; +[Flags] +internal enum InCheckResult +{ + NobodyInCheck = 1, // This kinda doesn't make sense from a Flags perspective, but it works. =/ + Player1InCheck = 2, + Player2InCheck = 4 +} + +internal enum GameOverResult +{ + GameIsNotOver, + Player1Wins, + Player2Wins +} + public enum WhichPiece { King, diff --git a/Shogi.Domain/ValueObjects/InCheckResult.cs b/Shogi.Domain/ValueObjects/InCheckResult.cs deleted file mode 100644 index c5b54da..0000000 --- a/Shogi.Domain/ValueObjects/InCheckResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Shogi.Domain.ValueObjects; - -[Flags] -internal enum InCheckResult -{ - NobodyInCheck = 1, // This kinda doesn't make sense from a Flags perspective, but it works. =/ - Player1InCheck = 2, - Player2InCheck = 4 -} diff --git a/Shogi.Domain/ValueObjects/ShogiBoard.cs b/Shogi.Domain/ValueObjects/ShogiBoard.cs index 2f78461..cb7ca43 100644 --- a/Shogi.Domain/ValueObjects/ShogiBoard.cs +++ b/Shogi.Domain/ValueObjects/ShogiBoard.cs @@ -59,34 +59,7 @@ public sealed class ShogiBoard if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play."); // Look for threats against the kings. - var inCheckResult = simState.State - .Where(kvp => kvp.Value != null) - .Cast>() - .Aggregate(InCheckResult.NobodyInCheck, (inCheckResult, kvp) => - { - var newInCheckResult = inCheckResult; - var threatPiece = kvp.Value; - var opposingKingPosition = Notation.FromBoardNotation(kings.Single(king => king.Value.Owner != threatPiece.Owner).Key); - var candidatePositions = threatPiece.GetPathFromStartToEnd(Notation.FromBoardNotation(kvp.Key), opposingKingPosition); - - foreach (var position in candidatePositions) - { - // No piece at this position, so pathing is unobstructed. Continue pathing. - if (simState[position] == null) continue; - - var pieceAtPosition = simState[position]!; - if (pieceAtPosition.WhichPiece == WhichPiece.King && pieceAtPosition.Owner != threatPiece.Owner) - { - newInCheckResult &= pieceAtPosition.Owner == WhichPlayer.Player1 ? InCheckResult.Player2InCheck : InCheckResult.Player1InCheck; - } - else - { - break; - } - } - - return newInCheckResult; - }); + InCheckResult inCheckResult = IsEitherPlayerInCheck(simState, kings); var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 ? inCheckResult.HasFlag(InCheckResult.Player1InCheck) @@ -96,12 +69,12 @@ public sealed class ShogiBoard { return new MoveResult(false, "This move puts the moving player in check, which is illega."); } - - // Move is legal; mutate the real state. - BoardState.Move(from, to, isPromotion); var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 ? inCheckResult.HasFlag(InCheckResult.Player2InCheck) : inCheckResult.HasFlag(InCheckResult.Player1InCheck); + + // Move is legal; mutate the real state. + BoardState.Move(from, to, isPromotion); if (playerPutOpponentInCheck) { BoardState.InCheck = BoardState.WhoseTurn == WhichPlayer.Player1 @@ -112,15 +85,6 @@ public sealed class ShogiBoard // TODO: Look for check-mate. return new MoveResult(true); - - - - - - - - - //var simulation = new StandardRules(simState); //// If already in check, assert the move that resulted in check no longer results in check. //if (BoardState.InCheck == BoardState.WhoseTurn @@ -233,6 +197,44 @@ public sealed class ShogiBoard //BoardState.WhoseTurn = otherPlayer; } + + public GameOverResult EvaluateGameOver() + { + + } + + private static InCheckResult IsEitherPlayerInCheck(BoardState simState, KeyValuePair[] kings) + { + return simState.State + .Where(kvp => kvp.Value != null) + .Cast>() + .Aggregate(InCheckResult.NobodyInCheck, (inCheckResult, kvp) => + { + var newInCheckResult = inCheckResult; + var threatPiece = kvp.Value; + var opposingKingPosition = Notation.FromBoardNotation(kings.Single(king => king.Value.Owner != threatPiece.Owner).Key); + var positionsThreatened = threatPiece.GetPathFromStartToEnd(Notation.FromBoardNotation(kvp.Key), opposingKingPosition); + + foreach (var position in positionsThreatened) + { + // No piece at this position, so pathing is unobstructed. Continue pathing. + if (simState[position] == null) continue; + + var threatenedPiece = simState[position]!; + if (threatenedPiece.WhichPiece == WhichPiece.King && threatenedPiece.Owner != threatPiece.Owner) + { + newInCheckResult |= threatenedPiece.Owner == WhichPlayer.Player1 ? InCheckResult.Player1InCheck : InCheckResult.Player2InCheck; + } + else + { + break; + } + } + + return newInCheckResult; + }); + } + /// /// The purpose is to ensure a proposed board move is valid with regard to the moved piece's rules. /// This event does not worry about check or check-mate, or if a move is legal according to all Shogi rules. diff --git a/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor b/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor index 5cfcc63..d86fbaa 100644 --- a/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor +++ b/Shogi.UI/Pages/Play/GameBoard/GameBoardPresentation.razor @@ -19,7 +19,7 @@ var position = $"{file}{rank}"; var piece = Session?.BoardState.Board[position]; var isSelected = piece != null && SelectedPosition == position; -
@@ -80,7 +80,7 @@
- @if (this.OnClickJoinGame != null && string.IsNullOrEmpty(Session.Player2) && !string.IsNullOrEmpty(Session.Player1)) + @if (string.IsNullOrEmpty(Session.Player2) && !string.IsNullOrEmpty(Session.Player1)) {
@@ -121,9 +121,9 @@ [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 EventCallback OnClickTile { get; set; } + [Parameter] public EventCallback OnClickHand { get; set; } + [Parameter] public EventCallback OnClickJoinGame { get; set; } [Parameter] public bool IsMyTurn { get; set; } [Parameter] public bool UseSideboard { get; set; } = true; @@ -167,7 +167,9 @@ } } - private Action OnClickTileInternal(Piece? piece, string position) => () => OnClickTile?.Invoke(piece, position); - private Action OnClickHandInternal(Piece piece) => () => OnClickHand?.Invoke(piece); - private void OnClickJoinGameInternal() => OnClickJoinGame?.Invoke(); + private Func OnClickTileInternal(string position) => () => OnClickTile.InvokeAsync(position); + + private Func OnClickHandInternal(Piece piece) => () => OnClickHand.InvokeAsync(piece); + + private Task OnClickJoinGameInternal() => OnClickJoinGame.InvokeAsync(); } diff --git a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor index 909dc79..1a2c285 100644 --- a/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor +++ b/Shogi.UI/Pages/Play/GameBoard/SeatedGameBoard.razor @@ -50,7 +50,7 @@ } } - bool ShouldPromptForPromotion(string position) + bool IsWithinPromoteArea(string position) { if (Perspective == WhichPlayer.Player1 && Regex.IsMatch(position, ".[7-9]")) { @@ -63,11 +63,11 @@ return false; } - async Task OnClickTile(Piece? pieceAtPosition, string position) + async Task OnClickTile(string position) { - if (!IsMyTurn) return; - + if (!IsMyTurn || showPromotePrompt) return; + var pieceAtPosition = Session.BoardState.Board[position]; if (selectedBoardPosition == position) { // Deselect the selected position. @@ -105,12 +105,13 @@ if (selectedBoardPosition != null) { - Console.WriteLine("pieceAtPosition is null? {0}", pieceAtPosition == null); - if (pieceAtPosition == null || pieceAtPosition?.Owner != Perspective) { + var pieceBeingMoved = Session.BoardState.Board[selectedBoardPosition]; + var isPromotedAlready = pieceBeingMoved != null && pieceBeingMoved.IsPromoted; + Console.WriteLine("Is promoted? {0}", isPromotedAlready); // Moving to an empty space or capturing an opponent's piece. - if (ShouldPromptForPromotion(position) || ShouldPromptForPromotion(selectedBoardPosition)) + if (!isPromotedAlready && (IsWithinPromoteArea(position) || IsWithinPromoteArea(selectedBoardPosition))) { Console.WriteLine("Prompt!"); moveTo = position; @@ -118,11 +119,7 @@ } else { - - Console.WriteLine("OnClick to move to {0}", position); var success = await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, position, false)); - - Console.WriteLine("Success? {0}", success); if (!success) { selectedBoardPosition = null; @@ -134,9 +131,9 @@ } } - async Task OnClickHand(Piece piece) + void OnClickHand(Piece piece) { - if (!IsMyTurn) return; + if (!IsMyTurn || showPromotePrompt) return; // Prevent selecting from both the hand and the board. selectedBoardPosition = null; @@ -149,11 +146,13 @@ StateHasChanged(); } - private Task OnClickPromotionChoice(bool shouldPromote) + private async Task OnClickPromotionChoice(bool shouldPromote) { - if (selectedBoardPosition == null && selectedPieceFromHand.HasValue && moveTo != null) + if (selectedBoardPosition != null && moveTo != null) { - return ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedPieceFromHand.Value, moveTo)); + await ShogiApi.Move(Session.SessionId, new MovePieceCommand(selectedBoardPosition, moveTo, shouldPromote)); + showPromotePrompt = false; + return; } throw new InvalidOperationException("Unexpected scenario during OnClickPromotionChoice."); diff --git a/Shogi.UI/Pages/Play/GameBoard/TestCbParams.cs b/Shogi.UI/Pages/Play/GameBoard/TestCbParams.cs new file mode 100644 index 0000000..df0d7dc --- /dev/null +++ b/Shogi.UI/Pages/Play/GameBoard/TestCbParams.cs @@ -0,0 +1,7 @@ +namespace Shogi.UI.Pages.Play.GameBoard +{ + public class TestCbParams + { + public string Name { get; set; } + } +} diff --git a/Shogi.UI/Shogi.UI.csproj b/Shogi.UI/Shogi.UI.csproj index a2cdee1..a44d5de 100644 --- a/Shogi.UI/Shogi.UI.csproj +++ b/Shogi.UI/Shogi.UI.csproj @@ -26,16 +26,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj index 3d612b4..b84ee1c 100644 --- a/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj +++ b/Tests/AcceptanceTests/Shogi.AcceptanceTests.csproj @@ -25,20 +25,20 @@ - + - + - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/UnitTests/UnitTests.csproj b/Tests/UnitTests/UnitTests.csproj index 5572e69..69bf354 100644 --- a/Tests/UnitTests/UnitTests.csproj +++ b/Tests/UnitTests/UnitTests.csproj @@ -8,14 +8,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all