|
|
|
@@ -9,7 +9,7 @@ namespace Shogi.Domain.ValueObjects;
|
|
|
|
public sealed class ShogiBoard
|
|
|
|
public sealed class ShogiBoard
|
|
|
|
{
|
|
|
|
{
|
|
|
|
private readonly StandardRules rules;
|
|
|
|
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)
|
|
|
|
public ShogiBoard(BoardState initialState)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@@ -19,6 +19,8 @@ public sealed class ShogiBoard
|
|
|
|
|
|
|
|
|
|
|
|
public BoardState BoardState { get; }
|
|
|
|
public BoardState BoardState { get; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
|
|
|
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
@@ -27,7 +29,7 @@ public sealed class ShogiBoard
|
|
|
|
/// validate legal vs illegal moves without having to worry about reverting board state.
|
|
|
|
/// validate legal vs illegal moves without having to worry about reverting board state.
|
|
|
|
/// </remarks>
|
|
|
|
/// </remarks>
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
|
|
public MoveResult Move(string from, string to, bool isPromotion = false)
|
|
|
|
public MoveResult Move(string from, string to, bool isPromotion)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Validate the move
|
|
|
|
// Validate the move
|
|
|
|
var moveResult = IsMoveValid(Notation.FromBoardNotation(from), Notation.FromBoardNotation(to));
|
|
|
|
var moveResult = IsMoveValid(Notation.FromBoardNotation(from), Notation.FromBoardNotation(to));
|
|
|
|
@@ -51,15 +53,8 @@ public sealed class ShogiBoard
|
|
|
|
return moveResult;
|
|
|
|
return moveResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var kings = simState.State
|
|
|
|
|
|
|
|
.Where(kvp => kvp.Value?.WhichPiece == WhichPiece.King)
|
|
|
|
|
|
|
|
.Cast<KeyValuePair<string, Piece>>()
|
|
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play.");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Look for threats against the kings.
|
|
|
|
// Look for threats against the kings.
|
|
|
|
InCheckResult inCheckResult = IsEitherPlayerInCheck(simState, kings);
|
|
|
|
InCheckResult inCheckResult = IsEitherPlayerInCheck(simState);
|
|
|
|
|
|
|
|
|
|
|
|
var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
|
|
|
var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
|
|
|
? inCheckResult.HasFlag(InCheckResult.Player1InCheck)
|
|
|
|
? inCheckResult.HasFlag(InCheckResult.Player1InCheck)
|
|
|
|
@@ -67,7 +62,7 @@ public sealed class ShogiBoard
|
|
|
|
|
|
|
|
|
|
|
|
if (playerPutThemselfInCheck)
|
|
|
|
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
|
|
|
|
var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
|
|
|
? inCheckResult.HasFlag(InCheckResult.Player2InCheck)
|
|
|
|
? inCheckResult.HasFlag(InCheckResult.Player2InCheck)
|
|
|
|
@@ -82,105 +77,93 @@ public sealed class ShogiBoard
|
|
|
|
: WhichPlayer.Player1;
|
|
|
|
: WhichPlayer.Player1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Look for check-mate.
|
|
|
|
|
|
|
|
return new MoveResult(true);
|
|
|
|
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);
|
|
|
|
var index = BoardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand);
|
|
|
|
//if (index == -1)
|
|
|
|
if (index == -1)
|
|
|
|
//{
|
|
|
|
{
|
|
|
|
// throw new InvalidOperationException($"{pieceInHand} does not exist in the hand.");
|
|
|
|
return new MoveResult(false, $"{pieceInHand} does not exist in the hand.");
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//if (BoardState[to] != null)
|
|
|
|
if (BoardState[to] != null)
|
|
|
|
//{
|
|
|
|
{
|
|
|
|
// throw new InvalidOperationException("Illegal placement of piece from the hand. Destination is not empty.");
|
|
|
|
return new MoveResult(false, $"Tried to play a piece from the hand to an occupied position.");
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//var toVector = Notation.FromBoardNotation(to);
|
|
|
|
var toVector = Notation.FromBoardNotation(to);
|
|
|
|
//switch (pieceInHand)
|
|
|
|
switch (pieceInHand)
|
|
|
|
//{
|
|
|
|
{
|
|
|
|
// case WhichPiece.Knight:
|
|
|
|
case WhichPiece.Knight:
|
|
|
|
// {
|
|
|
|
{
|
|
|
|
// // Knight cannot be placed onto the farthest two ranks from the hand.
|
|
|
|
// Knight cannot be placed onto the farthest two ranks from the hand.
|
|
|
|
// if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6
|
|
|
|
if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6
|
|
|
|
// || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2)
|
|
|
|
|| BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2)
|
|
|
|
// {
|
|
|
|
{
|
|
|
|
// throw new InvalidOperationException("Illegal move. Knight has no valid moves after placement.");
|
|
|
|
return new MoveResult(false, "Illegal move. Knight has no valid moves after placement.");
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
// break;
|
|
|
|
break;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
// case WhichPiece.Lance:
|
|
|
|
case WhichPiece.Lance:
|
|
|
|
// case WhichPiece.Pawn:
|
|
|
|
case WhichPiece.Pawn:
|
|
|
|
// {
|
|
|
|
{
|
|
|
|
// // Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
|
|
|
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
|
|
|
// if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8
|
|
|
|
if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8
|
|
|
|
// || BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0)
|
|
|
|
|| BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0)
|
|
|
|
// {
|
|
|
|
{
|
|
|
|
// throw new InvalidOperationException($"Illegal move. {pieceInHand} has no valid moves after placement.");
|
|
|
|
return new MoveResult(false, $"Illegal move. {pieceInHand} has no valid moves after placement.");
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
// break;
|
|
|
|
break;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//var tempBoard = new BoardState(BoardState);
|
|
|
|
if (pieceInHand == WhichPiece.Pawn)
|
|
|
|
//var simulation = new StandardRules(tempBoard);
|
|
|
|
{
|
|
|
|
//var moveResult = simulation.Move(pieceInHand, to);
|
|
|
|
// Pawns cannot be placed into a column with another unpromoted pawn controlled by the moving player.
|
|
|
|
//if (!moveResult.Success)
|
|
|
|
var columnAlreadyHasPawn = zeroToEight
|
|
|
|
//{
|
|
|
|
.Select(y => BoardState[new Vector2(toVector.X, y)])
|
|
|
|
// throw new InvalidOperationException(moveResult.Reason);
|
|
|
|
.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 (columnAlreadyHasPawn)
|
|
|
|
//if (BoardState.InCheck == BoardState.WhoseTurn
|
|
|
|
{
|
|
|
|
// && simulation.IsOpposingKingThreatenedByPosition(BoardState.PreviousMove.To))
|
|
|
|
return new MoveResult(false, "A player may not have two unpromoted pawns in the same file.");
|
|
|
|
//{
|
|
|
|
}
|
|
|
|
// throw new InvalidOperationException("Unable to drop piece becauase you are still in check.");
|
|
|
|
}
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//if (simulation.DidPlayerPutThemselfInCheck())
|
|
|
|
var simState = new BoardState(BoardState);
|
|
|
|
//{
|
|
|
|
var moveResult = simState.Move(pieceInHand, to);
|
|
|
|
// throw new InvalidOperationException("Illegal move. This move places you in check.");
|
|
|
|
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())
|
|
|
|
//if (rules.IsOpponentInCheckAfterMove())
|
|
|
|
//{
|
|
|
|
//{
|
|
|
|
// BoardState.InCheck = otherPlayer;
|
|
|
|
// BoardState.InCheck = otherPlayer;
|
|
|
|
@@ -191,20 +174,24 @@ public sealed class ShogiBoard
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//var kingPosition = otherPlayer == WhichPlayer.Player1
|
|
|
|
return new MoveResult(true);
|
|
|
|
// ? tempBoard.Player1KingPosition
|
|
|
|
|
|
|
|
// : tempBoard.Player2KingPosition;
|
|
|
|
|
|
|
|
//BoardState.WhoseTurn = otherPlayer;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public GameOverResult EvaluateGameOver()
|
|
|
|
public GameOverResult EvaluateGameOver()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
return GameOverResult.GameIsNotOver;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static InCheckResult IsEitherPlayerInCheck(BoardState simState, KeyValuePair<string, Piece>[] kings)
|
|
|
|
private static InCheckResult IsEitherPlayerInCheck(BoardState simState)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
var kings = simState.State
|
|
|
|
|
|
|
|
.Where(kvp => kvp.Value?.WhichPiece == WhichPiece.King)
|
|
|
|
|
|
|
|
.Cast<KeyValuePair<string, Piece>>()
|
|
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play.");
|
|
|
|
|
|
|
|
|
|
|
|
return simState.State
|
|
|
|
return simState.State
|
|
|
|
.Where(kvp => kvp.Value != null)
|
|
|
|
.Where(kvp => kvp.Value != null)
|
|
|
|
.Cast<KeyValuePair<string, Piece>>()
|
|
|
|
.Cast<KeyValuePair<string, Piece>>()
|
|
|
|
|