using System.Numerics; namespace Shogi.Domain { /// /// Facilitates Shogi board state transitions, cognisant of Shogi rules. /// The board is always from Player1's perspective. /// [0,0] is the lower-left position, [8,8] is the higher-right position /// public sealed class Shogi { private readonly ShogiBoardState board; private readonly StandardRules rules; public string Error { get; private set; } public Shogi(ShogiBoardState board) { this.board = board; rules = new StandardRules(this.board); Error = string.Empty; } public Shogi(IList moves) : this() { for (var i = 0; i < moves.Count; i++) { if (!Move(moves[i])) { // Todo: Add some smarts to know why a move was invalid. In check? Piece not found? etc. throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}. {Error}"); } } } public bool Move(Move move) { var moveSuccess = TryMove(move); if (!moveSuccess) { return false; } var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; if (EvaluateCheckAfterMove(move, otherPlayer)) { InCheck = otherPlayer; IsCheckmate = EvaluateCheckmate(); } else { InCheck = null; } return true; } /// /// Attempts a given move. Returns false if the move is illegal. /// private bool TryMove(Move move) { // Try making the move in a "throw away" board. var simulator = new StandardRules(new ShogiBoardState(this.board)); var simulatedMoveResults = move.PieceFromHand.HasValue ? simulator.PlaceFromHand(move) : simulator.PlaceFromBoard(move); if (!simulatedMoveResults) { // Surface the error description. Error = simulationBoard.Error; return false; } // If already in check, assert the move that resulted in check no longer results in check. if (InCheck == WhoseTurn) { if (simulationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn)) { // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn; return false; } } // The move is valid and legal; update board state. if (move.PieceFromHand.HasValue) PlaceFromHand(move); else PlaceFromBoard(move); return true; } } }