checkpoint
This commit is contained in:
257
Shogi.Domain/Session.cs
Normal file
257
Shogi.Domain/Session.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using Shogi.Domain.Pieces;
|
||||
using System.Text;
|
||||
|
||||
namespace Shogi.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public sealed class Session
|
||||
{
|
||||
private readonly Tuple<string, string> Players;
|
||||
private readonly BoardState boardState;
|
||||
private readonly StandardRules rules;
|
||||
private readonly SessionMetadata metadata;
|
||||
|
||||
public Session() : this(new BoardState())
|
||||
{
|
||||
}
|
||||
|
||||
public Session(BoardState state) : this(state, new SessionMetadata(string.Empty, false, string.Empty, string.Empty))
|
||||
{
|
||||
}
|
||||
|
||||
public Session(BoardState state, SessionMetadata metadata)
|
||||
{
|
||||
rules = new StandardRules(state);
|
||||
boardState = state;
|
||||
this.metadata = metadata;
|
||||
Players = new(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
public string Name => metadata.Name;
|
||||
public string Player1Name => metadata.Player1;
|
||||
public string? Player2Name => metadata.Player2;
|
||||
public IDictionary<string, Piece?> BoardState => boardState.State;
|
||||
public IList<Piece> Player1Hand => boardState.Player1Hand;
|
||||
public IList<Piece> Player2Hand => boardState.Player2Hand;
|
||||
public WhichPlayer? InCheck => boardState.InCheck;
|
||||
public bool IsCheckMate => boardState.IsCheckmate;
|
||||
|
||||
/// <summary>
|
||||
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The strategy involves simulating a move on a throw-away board state that can be used to
|
||||
/// validate legal vs illegal moves without having to worry about reverting board state.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public void Move(string from, string to, bool isPromotion)
|
||||
{
|
||||
var simulationState = new BoardState(boardState);
|
||||
var simulation = new StandardRules(simulationState);
|
||||
var moveResult = simulation.Move(from, to, isPromotion);
|
||||
if (!moveResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException(moveResult.Reason);
|
||||
}
|
||||
|
||||
// If already in check, assert the move that resulted in check no longer results in check.
|
||||
if (boardState.InCheck == boardState.WhoseTurn
|
||||
&& simulation.IsOpposingKingThreatenedByPosition(boardState.PreviousMoveTo))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to move because you are still in check.");
|
||||
}
|
||||
|
||||
var otherPlayer = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
||||
if (simulation.IsPlayerInCheckAfterMove())
|
||||
{
|
||||
throw new InvalidOperationException("Illegal move. This move places you in check.");
|
||||
}
|
||||
|
||||
_ = rules.Move(from, to, isPromotion);
|
||||
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)
|
||||
{
|
||||
var index = boardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand);
|
||||
if (index == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"{pieceInHand} does not exist in the hand.");
|
||||
}
|
||||
|
||||
if (boardState[to] != null)
|
||||
{
|
||||
throw new InvalidOperationException("Illegal placement of piece from the hand. Destination is not empty.");
|
||||
}
|
||||
|
||||
var toVector = Notation.FromBoardNotation(to);
|
||||
switch (pieceInHand)
|
||||
{
|
||||
case WhichPiece.Knight:
|
||||
{
|
||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||
if ((boardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6)
|
||||
|| (boardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2))
|
||||
{
|
||||
throw new InvalidOperationException("Illegal move. Knight has no valid moves after placement.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WhichPiece.Lance:
|
||||
case WhichPiece.Pawn:
|
||||
{
|
||||
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
||||
if ((boardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8)
|
||||
|| (boardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0))
|
||||
{
|
||||
throw new InvalidOperationException($"Illegal move. {pieceInHand} has no valid moves after placement.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tempBoard = new BoardState(boardState);
|
||||
var simulation = new StandardRules(tempBoard);
|
||||
var moveResult = simulation.Move(pieceInHand, to);
|
||||
if (!moveResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException(moveResult.Reason);
|
||||
}
|
||||
|
||||
var otherPlayer = tempBoard.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
||||
if (boardState.InCheck == boardState.WhoseTurn)
|
||||
{
|
||||
//if (simulation.IsPlayerInCheckAfterMove(boardState.PreviousMoveTo, toVector, boardState.WhoseTurn))
|
||||
//{
|
||||
// throw new InvalidOperationException("Illegal move. You're still in check!");
|
||||
//}
|
||||
}
|
||||
|
||||
var kingPosition = otherPlayer == WhichPlayer.Player1 ? tempBoard.Player1KingPosition : tempBoard.Player2KingPosition;
|
||||
//if (simulation.IsPlayerInCheckAfterMove(toVector, kingPosition, otherPlayer))
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//rules.Move(from, to, isPromotion);
|
||||
//if (rules.IsPlayerInCheckAfterMove(fromVector, toVector, otherPlayer))
|
||||
//{
|
||||
// board.InCheck = otherPlayer;
|
||||
// board.IsCheckmate = rules.EvaluateCheckmate();
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// board.InCheck = null;
|
||||
//}
|
||||
boardState.WhoseTurn = otherPlayer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a ASCII representation of the board for debugging board state.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToStringStateAsAscii()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(" ");
|
||||
builder.Append("Player 2(.)");
|
||||
builder.AppendLine();
|
||||
for (var rank = 8; rank >= 0; rank--)
|
||||
{
|
||||
// Horizontal line
|
||||
builder.Append(" - ");
|
||||
for (var file = 0; file < 8; file++) builder.Append("- - ");
|
||||
builder.Append("- -");
|
||||
|
||||
// Print Rank ruler.
|
||||
builder.AppendLine();
|
||||
builder.Append($"{rank + 1} ");
|
||||
|
||||
// Print pieces.
|
||||
builder.Append(" |");
|
||||
for (var x = 0; x < 9; x++)
|
||||
{
|
||||
var piece = boardState[x, rank];
|
||||
if (piece == null)
|
||||
{
|
||||
builder.Append(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendFormat("{0}", ToAscii(piece));
|
||||
}
|
||||
builder.Append('|');
|
||||
}
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
// Horizontal line
|
||||
builder.Append(" - ");
|
||||
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
||||
builder.Append("- -");
|
||||
builder.AppendLine();
|
||||
builder.Append(" ");
|
||||
builder.Append("Player 1");
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
// Print File ruler.
|
||||
builder.Append(" ");
|
||||
builder.Append(" A B C D E F G H I ");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="piece"></param>
|
||||
/// <returns>
|
||||
/// A string with three characters.
|
||||
/// The first character indicates promotion status.
|
||||
/// The second character indicates piece.
|
||||
/// The third character indicates ownership.
|
||||
/// </returns>
|
||||
public static string ToAscii(Piece piece)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
if (piece.IsPromoted) builder.Append('^');
|
||||
else builder.Append(' ');
|
||||
|
||||
var name = piece.WhichPiece switch
|
||||
{
|
||||
WhichPiece.King => "K",
|
||||
WhichPiece.GoldGeneral => "G",
|
||||
WhichPiece.SilverGeneral => "S",
|
||||
WhichPiece.Bishop => "B",
|
||||
WhichPiece.Rook => "R",
|
||||
WhichPiece.Knight => "k",
|
||||
WhichPiece.Lance => "L",
|
||||
WhichPiece.Pawn => "P",
|
||||
_ => throw new ArgumentException($"Unknown value for {nameof(WhichPiece)}."),
|
||||
};
|
||||
builder.Append(name);
|
||||
|
||||
if (piece.Owner == WhichPlayer.Player2) builder.Append('.');
|
||||
else builder.Append(' ');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user