From f8bf967581ee2cd12cc91e0881c46797809c6c7e Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Sat, 26 Oct 2024 22:09:41 -0500 Subject: [PATCH] yep --- Shogi.Domain/ValueObjects/BoardState.cs | 10 -- Shogi.Domain/ValueObjects/Piece.cs | 24 ++++- Shogi.Domain/ValueObjects/ShogiBoard.cs | 94 +++++++++++++++++-- .../YetToBeAssimilatedIntoDDD/Pathing/Path.cs | 32 +------ 4 files changed, 107 insertions(+), 53 deletions(-) diff --git a/Shogi.Domain/ValueObjects/BoardState.cs b/Shogi.Domain/ValueObjects/BoardState.cs index bf679ec..03a51a6 100644 --- a/Shogi.Domain/ValueObjects/BoardState.cs +++ b/Shogi.Domain/ValueObjects/BoardState.cs @@ -182,16 +182,6 @@ public class BoardState return new MoveResult(true); } - /// - /// Returns true if the given path can be traversed without colliding into a piece. - /// - public bool IsPathBlocked(IEnumerable path) - { - return !path.Any() - || path.SkipLast(1).Any(position => this[position] != null) - || this[path.Last()]?.Owner == WhoseTurn; - } - internal bool IsWithinPromotionZone(Vector2 position) { // TODO: Move this promotion zone logic into the StandardRules class. diff --git a/Shogi.Domain/ValueObjects/Piece.cs b/Shogi.Domain/ValueObjects/Piece.cs index 42316bd..51826d8 100644 --- a/Shogi.Domain/ValueObjects/Piece.cs +++ b/Shogi.Domain/ValueObjects/Piece.cs @@ -25,8 +25,6 @@ namespace Shogi.Domain.ValueObjects public WhichPiece WhichPiece { get; } public WhichPlayer Owner { get; private set; } public bool IsPromoted { get; private set; } - public bool IsUpsideDown => Owner == WhichPlayer.Player2; - protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) { WhichPiece = piece; @@ -60,7 +58,7 @@ namespace Shogi.Domain.ValueObjects { var steps = new List(10); - var path = MoveSet.GetNearestPath(start, end); + var path = GetNearestPath(MoveSet, start, end); var position = start; while (Vector2.Distance(start, position) < Vector2.Distance(start, end)) { @@ -77,5 +75,25 @@ namespace Shogi.Domain.ValueObjects 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/ShogiBoard.cs b/Shogi.Domain/ValueObjects/ShogiBoard.cs index 49a8035..1ddfa64 100644 --- a/Shogi.Domain/ValueObjects/ShogiBoard.cs +++ b/Shogi.Domain/ValueObjects/ShogiBoard.cs @@ -1,4 +1,5 @@ using Shogi.Domain.YetToBeAssimilatedIntoDDD; +using System.Threading.Tasks; namespace Shogi.Domain.ValueObjects; /// @@ -8,18 +9,16 @@ namespace Shogi.Domain.ValueObjects; /// public sealed class ShogiBoard { - private readonly StandardRules rules; + private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8]; private static readonly Vector2 BoardSize = new(9, 9); public ShogiBoard(BoardState initialState) { BoardState = initialState; - rules = new StandardRules(BoardState); } 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. @@ -164,23 +163,100 @@ public sealed class ShogiBoard : WhichPlayer.Player1; } - //if (rules.IsOpponentInCheckAfterMove()) - //{ - // BoardState.InCheck = otherPlayer; - // // A pawn, placed from the hand, cannot be the cause of checkmate. + // A pawn, placed from the hand, cannot be the cause of checkmate. // if (rules.IsOpponentInCheckMate() && pieceInHand != WhichPiece.Pawn) // { // BoardState.IsCheckmate = true; // } - //} return new MoveResult(true); } - public GameOverResult EvaluateGameOver() + private async Task EvaluateGameOver() { + + if (!BoardState.InCheck.HasValue) + { + return GameOverResult.GameIsNotOver; + } + var kingInCheck = BoardState.State + .Single(kvp => kvp.Value?.WhichPiece == WhichPiece.King && kvp.Value?.Owner == BoardState.InCheck); + var kingInCheckVectorPosition = Notation.FromBoardNotation(kingInCheck.Key); + + var piecesOfPlayerInCheck = BoardState.State + .Where(kvp => kvp.Value?.Owner == BoardState.InCheck) + .Cast>(); + + foreach (var (notation, piece) in piecesOfPlayerInCheck) + { + // Get possible locations this piece could move to. + var allPossibleMoves = GetPossiblePositionsForPiece(Notation.FromBoardNotation(notation), piece); + // Try to make a legal move, disproving checkmate. + foreach (var move in allPossibleMoves) + { + var simState = new BoardState(BoardState); + simState.Move(notation, Notation.ToBoardNotation(move), false); + var inCheckResult = IsEitherPlayerInCheck(simState); + if (inCheckResult == InCheckResult.) + } + } + + // while (tasks.Any()) var result = Task.WhenAny(tasks); // Then check for GameIsNotOver and maybe return early. + + + var gameOverResult = BoardState.State + .Where(kvp => kvp.Value != null) + .Cast>() + .Aggregate(GameOverResult.GameIsNotOver, (inCheckResult, kvp) => + { + var newInCheckResult = inCheckResult; + var threatPiece = kvp.Value; + var opposingKingPosition = Notation.FromBoardNotation(kingInCheck.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; + }); + return GameOverResult.GameIsNotOver; + + Vector2[] GetPossiblePositionsForPiece(Vector2 piecePosition, Piece piece) + { + var paths = piece.MoveSet; + return + paths + .SelectMany(path => + { + var list = new List(10); + var position = path.Step + piecePosition; + while (position.IsInsideBoardBoundary()) + { + list.Add(position); + position += path.Step; + } + + return list; + }) + // Where tile at position is empty, meaning the piece could move there. + .Where(newPosition => BoardState[newPosition] == null) + .ToArray(); + } } private static InCheckResult IsEitherPlayerInCheck(BoardState simState) diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs b/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs index c4bf9be..4be696e 100644 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs +++ b/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs @@ -20,34 +20,4 @@ public record Path Step = step; this.Distance = distance; } - public Path Invert() => new(Vector2.Negate(Step), Distance); - - //public enum PathingResult - //{ - // Obstructed, - // CompletedWithoutObstruction - //} -} - -public static class PathExtensions -{ - public static Path GetNearestPath(this 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; - } -} + public Path Invert() => new(Vector2.Negate(Step), Distance); \ No newline at end of file