From 550942281bd013b7653088f67500765c8bd1db52 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Fri, 25 Oct 2024 23:46:14 -0500 Subject: [PATCH] Fix Hand css. Implement placing from hand. --- Shogi.Api/Repositories/EmailSender.cs | 1 - Shogi.Domain/ValueObjects/Enums.cs | 2 +- Shogi.Domain/ValueObjects/ShogiBoard.cs | 199 ++++++++---------- .../GameBoard/GameboardPresentation.razor.css | 22 +- 4 files changed, 105 insertions(+), 119 deletions(-) diff --git a/Shogi.Api/Repositories/EmailSender.cs b/Shogi.Api/Repositories/EmailSender.cs index 828d878..84c1e69 100644 --- a/Shogi.Api/Repositories/EmailSender.cs +++ b/Shogi.Api/Repositories/EmailSender.cs @@ -13,7 +13,6 @@ public class EmailSender : IEmailSender private readonly HttpClient client; private string apiKey; - public EmailSender(HttpClient client, IOptionsMonitor apiKeys) { this.apiKey = apiKeys.CurrentValue.BrevoEmailService; diff --git a/Shogi.Domain/ValueObjects/Enums.cs b/Shogi.Domain/ValueObjects/Enums.cs index 290c143..0bc6749 100644 --- a/Shogi.Domain/ValueObjects/Enums.cs +++ b/Shogi.Domain/ValueObjects/Enums.cs @@ -8,7 +8,7 @@ internal enum InCheckResult Player2InCheck = 4 } -internal enum GameOverResult +public enum GameOverResult { GameIsNotOver, Player1Wins, diff --git a/Shogi.Domain/ValueObjects/ShogiBoard.cs b/Shogi.Domain/ValueObjects/ShogiBoard.cs index cb7ca43..49a8035 100644 --- a/Shogi.Domain/ValueObjects/ShogiBoard.cs +++ b/Shogi.Domain/ValueObjects/ShogiBoard.cs @@ -9,7 +9,7 @@ namespace Shogi.Domain.ValueObjects; public sealed class ShogiBoard { private readonly StandardRules rules; - private static readonly Vector2 BoardSize = new Vector2(9, 9); + private static readonly Vector2 BoardSize = new(9, 9); public ShogiBoard(BoardState initialState) { @@ -19,6 +19,8 @@ public sealed class ShogiBoard public BoardState BoardState { get; } + private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8]; + /// /// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game. /// @@ -27,7 +29,7 @@ public sealed class ShogiBoard /// validate legal vs illegal moves without having to worry about reverting board state. /// /// - public MoveResult Move(string from, string to, bool isPromotion = false) + public MoveResult Move(string from, string to, bool isPromotion) { // Validate the move var moveResult = IsMoveValid(Notation.FromBoardNotation(from), Notation.FromBoardNotation(to)); @@ -51,15 +53,8 @@ public sealed class ShogiBoard return moveResult; } - var kings = simState.State - .Where(kvp => kvp.Value?.WhichPiece == WhichPiece.King) - .Cast>() - .ToArray(); - - if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play."); - // Look for threats against the kings. - InCheckResult inCheckResult = IsEitherPlayerInCheck(simState, kings); + InCheckResult inCheckResult = IsEitherPlayerInCheck(simState); var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 ? inCheckResult.HasFlag(InCheckResult.Player1InCheck) @@ -67,7 +62,7 @@ public sealed class ShogiBoard if (playerPutThemselfInCheck) { - return new MoveResult(false, "This move puts the moving player in check, which is illega."); + return new MoveResult(false, "This move puts the moving player in check, which is illegal."); } var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 ? inCheckResult.HasFlag(InCheckResult.Player2InCheck) @@ -82,105 +77,93 @@ public sealed class ShogiBoard : WhichPlayer.Player1; } - // 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 - // && simulation.IsOpposingKingThreatenedByPosition(BoardState.PreviousMove.To)) - //{ - // throw new InvalidOperationException("Unable to move because you are still in check."); - //} - - //if (simulation.DidPlayerPutThemselfInCheck()) - //{ - // throw new InvalidOperationException("Illegal move. This move places you in check."); - //} - - //var otherPlayer = BoardState.WhoseTurn == WhichPlayer.Player1 - // ? WhichPlayer.Player2 - // : WhichPlayer.Player1; - //_ = BoardState.Move(from, to, isPromotion); // "Rules" should not be doing any data changes. "State" should do that. - //if (rules.IsOpponentInCheckAfterMove()) - //{ - // BoardState.InCheck = otherPlayer; - // if (rules.IsOpponentInCheckMate()) - // { - // BoardState.IsCheckmate = true; - // } - //} - //else - //{ - // BoardState.InCheck = null; - //} - //BoardState.WhoseTurn = otherPlayer; } - public void Move(WhichPiece pieceInHand, string to) + public MoveResult Move(WhichPiece pieceInHand, string to) { - //var index = BoardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand); - //if (index == -1) - //{ - // throw new InvalidOperationException($"{pieceInHand} does not exist in the hand."); - //} + var index = BoardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand); + if (index == -1) + { + return new MoveResult(false, $"{pieceInHand} does not exist in the hand."); + } - //if (BoardState[to] != null) - //{ - // throw new InvalidOperationException("Illegal placement of piece from the hand. Destination is not empty."); - //} + if (BoardState[to] != null) + { + return new MoveResult(false, $"Tried to play a piece from the hand to an occupied position."); + } - //var toVector = Notation.FromBoardNotation(to); - //switch (pieceInHand) - //{ - // case WhichPiece.Knight: - // { - // // Knight cannot be placed onto the farthest two ranks from the hand. - // if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6 - // || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2) - // { - // throw new InvalidOperationException("Illegal move. Knight has no valid moves after placement."); - // } - // break; - // } - // case WhichPiece.Lance: - // case WhichPiece.Pawn: - // { - // // Lance and Pawn cannot be placed onto the farthest rank from the hand. - // if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8 - // || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0) - // { - // throw new InvalidOperationException($"Illegal move. {pieceInHand} has no valid moves after placement."); - // } - // break; - // } - //} + var toVector = Notation.FromBoardNotation(to); + switch (pieceInHand) + { + case WhichPiece.Knight: + { + // Knight cannot be placed onto the farthest two ranks from the hand. + if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6 + || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2) + { + return new MoveResult(false, "Illegal move. Knight has no valid moves after placement."); + } + break; + } + case WhichPiece.Lance: + case WhichPiece.Pawn: + { + // Lance and Pawn cannot be placed onto the farthest rank from the hand. + if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8 + || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0) + { + return new MoveResult(false, $"Illegal move. {pieceInHand} has no valid moves after placement."); + } + break; + } + } - //var tempBoard = new BoardState(BoardState); - //var simulation = new StandardRules(tempBoard); - //var moveResult = simulation.Move(pieceInHand, to); - //if (!moveResult.Success) - //{ - // throw new InvalidOperationException(moveResult.Reason); - //} + if (pieceInHand == WhichPiece.Pawn) + { + // Pawns cannot be placed into a column with another unpromoted pawn controlled by the moving player. + var columnAlreadyHasPawn = zeroToEight + .Select(y => BoardState[new Vector2(toVector.X, y)]) + .Where(piece => piece?.WhichPiece == WhichPiece.Pawn) + .Where(piece => piece?.Owner == BoardState.WhoseTurn) + .Where(piece => piece?.IsPromoted == false) + .Any(); - //// If already in check, assert the move that resulted in check no longer results in check. - //if (BoardState.InCheck == BoardState.WhoseTurn - // && simulation.IsOpposingKingThreatenedByPosition(BoardState.PreviousMove.To)) - //{ - // throw new InvalidOperationException("Unable to drop piece becauase you are still in check."); - //} + if (columnAlreadyHasPawn) + { + return new MoveResult(false, "A player may not have two unpromoted pawns in the same file."); + } + } - //if (simulation.DidPlayerPutThemselfInCheck()) - //{ - // throw new InvalidOperationException("Illegal move. This move places you in check."); - //} + var simState = new BoardState(BoardState); + var moveResult = simState.Move(pieceInHand, to); + if (!moveResult.IsSuccess) + { + return moveResult; + } + + var inCheckResult = IsEitherPlayerInCheck(simState); + var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 + ? inCheckResult.HasFlag(InCheckResult.Player1InCheck) + : inCheckResult.HasFlag(InCheckResult.Player2InCheck); + + if (playerPutThemselfInCheck) + { + return new MoveResult(false, "This move puts the moving player in check, which is illegal."); + } + var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1 + ? inCheckResult.HasFlag(InCheckResult.Player2InCheck) + : inCheckResult.HasFlag(InCheckResult.Player1InCheck); + + // Move is legal; mutate the real state. + BoardState.Move(pieceInHand, to); + if (playerPutOpponentInCheck) + { + BoardState.InCheck = BoardState.WhoseTurn == WhichPlayer.Player1 + ? WhichPlayer.Player2 + : WhichPlayer.Player1; + } - //// Update the non-simulation board. - //var otherPlayer = tempBoard.WhoseTurn == WhichPlayer.Player1 - // ? WhichPlayer.Player2 - // : WhichPlayer.Player1; - //_ = rules.Move(pieceInHand, to); //if (rules.IsOpponentInCheckAfterMove()) //{ // BoardState.InCheck = otherPlayer; @@ -191,20 +174,24 @@ public sealed class ShogiBoard // } //} - //var kingPosition = otherPlayer == WhichPlayer.Player1 - // ? tempBoard.Player1KingPosition - // : tempBoard.Player2KingPosition; - //BoardState.WhoseTurn = otherPlayer; + return new MoveResult(true); } public GameOverResult EvaluateGameOver() { - + return GameOverResult.GameIsNotOver; } - private static InCheckResult IsEitherPlayerInCheck(BoardState simState, KeyValuePair[] kings) + private static InCheckResult IsEitherPlayerInCheck(BoardState simState) { + var kings = simState.State + .Where(kvp => kvp.Value?.WhichPiece == WhichPiece.King) + .Cast>() + .ToArray(); + + if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play."); + return simState.State .Where(kvp => kvp.Value != null) .Cast>() diff --git a/Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css b/Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css index 402334f..b895d02 100644 --- a/Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css +++ b/Shogi.UI/Pages/Play/GameBoard/GameboardPresentation.razor.css @@ -59,17 +59,17 @@ } - .board .tile { - display: grid; - place-content: center; - aspect-ratio: var(--ratio); - background-color: beige; - transition: filter linear 0.25s; - } +.tile { + display: grid; + place-content: center; + aspect-ratio: var(--ratio); + background-color: beige; + transition: filter linear 0.25s; +} - .board .tile[data-selected] { - filter: invert(0.8); - } + .tile[data-selected] { + filter: invert(0.8); + } .ruler { color: beige; @@ -110,4 +110,4 @@ grid-template-rows: 3rem; place-items: center start; padding: 0.5rem; - } \ No newline at end of file + }