using Shogi.Domain.Other; using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; using System.Diagnostics; namespace Shogi.Domain.ValueObjects { [DebuggerDisplay("{WhichPiece} {Owner}")] public abstract record class Piece : IRulesLifecycle { public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false) { return piece switch { WhichPiece.King => new King(owner, isPromoted), WhichPiece.GoldGeneral => new GoldGeneral(owner, isPromoted), WhichPiece.SilverGeneral => new SilverGeneral(owner, isPromoted), WhichPiece.Bishop => new Bishop(owner, isPromoted), WhichPiece.Rook => new Rook(owner, isPromoted), WhichPiece.Knight => new Knight(owner, isPromoted), WhichPiece.Lance => new Lance(owner, isPromoted), WhichPiece.Pawn => new Pawn(owner, isPromoted), _ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.") }; } public abstract IEnumerable MoveSet { get; } 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; Owner = owner; IsPromoted = isPromoted; } public bool CanPromote => !IsPromoted && WhichPiece != WhichPiece.King && WhichPiece != WhichPiece.GoldGeneral; public void Promote() => IsPromoted = CanPromote; /// /// Prep the piece for capture by changing ownership and demoting. /// public void Capture(WhichPlayer newOwner) { Owner = newOwner; IsPromoted = false; } /// /// Respecting the move-set of the Piece, collect all positions along the shortest path from start to end. /// Useful if you need to iterate a move-set. /// /// /// /// An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions. public IEnumerable GetPathFromStartToEnd(Vector2 start, Vector2 end) { var steps = new List(10); var path = MoveSet.GetNearestPath(start, end); var position = start; while (Vector2.Distance(start, position) < Vector2.Distance(start, end)) { position += path.NormalizedDirection; steps.Add(position); if (path.Distance == Distance.OneStep) break; } if (position == end) { return steps; } return Array.Empty(); } #region IRulesLifecycle public RulesLifecycleResult OnMoveValidation(MoveValidationContext ctx) { var paths = this.MoveSet; var matchingPaths = paths.Where(p => p.NormalizedDirection == Vector2.Normalize(ctx.To - ctx.From)); if (!matchingPaths.Any()) { return new RulesLifecycleResult(IsError: true, "Piece cannot move like that."); } var multiStepPaths = matchingPaths.Where(p => p.Distance == Distance.MultiStep).ToArray(); foreach (var path in multiStepPaths) { // Assert that no pieces exist along the from -> to path. var isPathObstructed = GetPositionsAlongPath(ctx.From, ctx.To, path) .Any(pos => ctx.BoardState[(int)pos.X, (int)pos.Y] != null); if (isPathObstructed) { return new RulesLifecycleResult(IsError: true, "Piece cannot move through other pieces."); } } var pieceAtTo = ctx.BoardState[(int)ctx.To.X, (int)ctx.To.Y]; if (pieceAtTo?.Owner == this.Owner) { return new RulesLifecycleResult(IsError: true, "Cannot capture your own pieces."); } return new RulesLifecycleResult(IsError: false); static IEnumerable GetPositionsAlongPath(Vector2 from, Vector2 to, Path path) { var next = from; while (next != to && next.X >= 0 && next.X < 9 && next.Y >= 0 && next.Y < 9) { next += path.NormalizedDirection; yield return next; } } } #endregion } }