using System.Collections.ObjectModel; using Shogi.Domain.YetToBeAssimilatedIntoDDD; using BoardTile = System.Collections.Generic.KeyValuePair; namespace Shogi.Domain.ValueObjects; public class BoardState { /// /// Board state before any moves have been made, using standard setup and rules. /// public static BoardState StandardStarting => new( state: BuildStandardStartingBoardState(), player1Hand: new(), player2Hand: new(), whoseTurn: WhichPlayer.Player1, playerInCheck: null, previousMove: new Move()); /// /// Key is position notation, such as "E4". /// private readonly Dictionary board; public BoardState( Dictionary state, List player1Hand, List player2Hand, WhichPlayer whoseTurn, WhichPlayer? playerInCheck, Move previousMove) { board = state; Player1Hand = player1Hand; Player2Hand = player2Hand; PreviousMove = previousMove; WhoseTurn = whoseTurn; InCheck = playerInCheck; } /// /// Copy constructor. /// public BoardState(BoardState other) { board = new(81); foreach (var kvp in other.board) { var piece = kvp.Value; board[kvp.Key] = piece == null ? null : Piece.Create(piece.WhichPiece, piece.Owner, piece.IsPromoted); } WhoseTurn = other.WhoseTurn; InCheck = other.InCheck; IsCheckmate = other.IsCheckmate; PreviousMove = other.PreviousMove; Player1Hand = new(other.Player1Hand); Player2Hand = new(other.Player2Hand); } public ReadOnlyDictionary State => new(board); public List ActivePlayerHand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand; public Vector2 Player1KingPosition => Notation.FromBoardNotation(board.Where(kvp => kvp.Value != null).Single(kvp => { var piece = kvp.Value; return piece!.IsKing() && piece!.Owner == WhichPlayer.Player1; }).Key); public Vector2 Player2KingPosition => Notation.FromBoardNotation(board.Where(kvp => kvp.Value != null).Single(kvp => { var piece = kvp.Value; return piece!.IsKing() && piece!.Owner == WhichPlayer.Player2; }).Key); public List Player1Hand { get; } public List Player2Hand { get; } public Move PreviousMove { get; set; } public WhichPlayer WhoseTurn { get; set; } public WhichPlayer? InCheck { get; set; } public bool IsCheckmate { get; set; } public Piece? this[string notation] { // TODO: Validate "notation" here and throw an exception if invalid. get => board[notation]; set => board[notation] = value; } public Piece? this[Vector2 vector] { get => this[Notation.ToBoardNotation(vector)]; set => this[Notation.ToBoardNotation(vector)] = value; } public Piece? this[int x, int y] { get => this[Notation.ToBoardNotation(x, y)]; set => this[Notation.ToBoardNotation(x, y)] = value; } /// /// 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. return WhoseTurn == WhichPlayer.Player1 && position.Y > 5 || WhoseTurn == WhichPlayer.Player2 && position.Y < 3; } internal static bool IsWithinBoardBoundary(Vector2 position) { return position.X <= 8 && position.X >= 0 && position.Y <= 8 && position.Y >= 0; } internal List GetTilesOccupiedBy(WhichPlayer whichPlayer) => board .Where(kvp => kvp.Value?.Owner == whichPlayer) .Select(kvp => new BoardTile(Notation.FromBoardNotation(kvp.Key), kvp.Value!)) .ToList(); internal void Capture(Vector2 to) { var piece = this[to]; if (piece == null) throw new InvalidOperationException("Cannot capture. Piece at position does not exist."); piece.Capture(WhoseTurn); ActivePlayerHand.Add(piece); } /// /// Does not include the start position. /// internal static IEnumerable GetPathAlongDirectionFromStartToEdgeOfBoard(Vector2 start, Vector2 direction) { var next = start; while (IsWithinBoardBoundary(next + direction)) { next += direction; yield return next; } } internal Piece? QueryFirstPieceInPath(IEnumerable path) { foreach (var step in path) { if (this[step] != null) return this[step]; } return null; } private static Dictionary BuildStandardStartingBoardState() { return new Dictionary(81) { ["A1"] = new Lance(WhichPlayer.Player1), ["B1"] = new Knight(WhichPlayer.Player1), ["C1"] = new SilverGeneral(WhichPlayer.Player1), ["D1"] = new GoldGeneral(WhichPlayer.Player1), ["E1"] = new King(WhichPlayer.Player1), ["F1"] = new GoldGeneral(WhichPlayer.Player1), ["G1"] = new SilverGeneral(WhichPlayer.Player1), ["H1"] = new Knight(WhichPlayer.Player1), ["I1"] = new Lance(WhichPlayer.Player1), ["A2"] = null, ["B2"] = new Bishop(WhichPlayer.Player1), ["C2"] = null, ["D2"] = null, ["E2"] = null, ["F2"] = null, ["G2"] = null, ["H2"] = new Rook(WhichPlayer.Player1), ["I2"] = null, ["A3"] = new Pawn(WhichPlayer.Player1), ["B3"] = new Pawn(WhichPlayer.Player1), ["C3"] = new Pawn(WhichPlayer.Player1), ["D3"] = new Pawn(WhichPlayer.Player1), ["E3"] = new Pawn(WhichPlayer.Player1), ["F3"] = new Pawn(WhichPlayer.Player1), ["G3"] = new Pawn(WhichPlayer.Player1), ["H3"] = new Pawn(WhichPlayer.Player1), ["I3"] = new Pawn(WhichPlayer.Player1), ["A4"] = null, ["B4"] = null, ["C4"] = null, ["D4"] = null, ["E4"] = null, ["F4"] = null, ["G4"] = null, ["H4"] = null, ["I4"] = null, ["A5"] = null, ["B5"] = null, ["C5"] = null, ["D5"] = null, ["E5"] = null, ["F5"] = null, ["G5"] = null, ["H5"] = null, ["I5"] = null, ["A6"] = null, ["B6"] = null, ["C6"] = null, ["D6"] = null, ["E6"] = null, ["F6"] = null, ["G6"] = null, ["H6"] = null, ["I6"] = null, ["A7"] = new Pawn(WhichPlayer.Player2), ["B7"] = new Pawn(WhichPlayer.Player2), ["C7"] = new Pawn(WhichPlayer.Player2), ["D7"] = new Pawn(WhichPlayer.Player2), ["E7"] = new Pawn(WhichPlayer.Player2), ["F7"] = new Pawn(WhichPlayer.Player2), ["G7"] = new Pawn(WhichPlayer.Player2), ["H7"] = new Pawn(WhichPlayer.Player2), ["I7"] = new Pawn(WhichPlayer.Player2), ["A8"] = null, ["B8"] = new Rook(WhichPlayer.Player2), ["C8"] = null, ["D8"] = null, ["E8"] = null, ["F8"] = null, ["G8"] = null, ["H8"] = new Bishop(WhichPlayer.Player2), ["I8"] = null, ["A9"] = new Lance(WhichPlayer.Player2), ["B9"] = new Knight(WhichPlayer.Player2), ["C9"] = new SilverGeneral(WhichPlayer.Player2), ["D9"] = new GoldGeneral(WhichPlayer.Player2), ["E9"] = new King(WhichPlayer.Player2), ["F9"] = new GoldGeneral(WhichPlayer.Player2), ["G9"] = new SilverGeneral(WhichPlayer.Player2), ["H9"] = new Knight(WhichPlayer.Player2), ["I9"] = new Lance(WhichPlayer.Player2) }; } }