Merged in better-communication (pull request #49)

Better communication
This commit is contained in:
2022-05-10 22:14:25 +00:00
parent 04f2d115ad
commit 8951cd4223
26 changed files with 1397 additions and 811 deletions

View File

@@ -1,4 +1,8 @@
namespace Shogi.Domain
using Shogi.Domain.Pieces;
using System;
using System.Text;
namespace Shogi.Domain
{
/// <summary>
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
@@ -7,106 +11,232 @@
/// </summary>
public sealed class Shogi
{
private readonly ShogiBoardState board;
private readonly ShogiBoardState boardState;
private readonly StandardRules rules;
public string Error { get; private set; }
public Shogi() : this(new ShogiBoardState())
{
}
public Shogi(ShogiBoardState board)
{
this.board = board;
rules = new StandardRules(this.board);
}
//public Shogi(IList<Move> moves) : this(new ShogiBoardState())
//{
// for (var i = 0; i < moves.Count; i++)
// {
// if (!Move(moves[i]))
// {
// throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}.");
// }
// }
//}
public MoveResult CanMove(string from, string to, bool isPromotion)
{
var simulator = new StandardRules(new ShogiBoardState(board));
return simulator.Move(from, to, isPromotion);
}
public MoveResult CanMove(WhichPiece pieceInHand, string to)
{
var simulator = new StandardRules(new ShogiBoardState(board));
return simulator.Move(pieceInHand, to);
this.boardState = board;
rules = new StandardRules(this.boardState);
}
/// <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 tempBoard = new ShogiBoardState(board);
var simulation = new StandardRules(tempBoard);
var simulationState = new ShogiBoardState(boardState);
var simulation = new StandardRules(simulationState);
var moveResult = simulation.Move(from, to, isPromotion);
if (!moveResult.Success)
{
throw new InvalidOperationException(moveResult.Reason);
}
var fromVector = ShogiBoardState.FromBoardNotation(from);
var toVector = ShogiBoardState.FromBoardNotation(to);
var otherPlayer = board.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
if (simulation.IsPlayerInCheckAfterMove(fromVector, toVector, board.WhoseTurn))
// 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.IsPlayerInCheckAfterMove(fromVector, toVector, otherPlayer))
_ = rules.Move(from, to, isPromotion);
if (rules.IsOpponentInCheckAfterMove())
{
board.InCheck = otherPlayer;
board.IsCheckmate = rules.EvaluateCheckmate();
boardState.InCheck = otherPlayer;
// TODO: evaluate checkmate.
//if (rules.IsOpponentInCheckMate())
//{
// boardState.IsCheckmate = true;
//}
}
else
{
board.InCheck = null;
boardState.InCheck = null;
}
board.WhoseTurn = otherPlayer;
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 = ShogiBoardState.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 ShogiBoardState(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>
///// Attempts a given move. Returns false if the move is illegal.
///// </summary>
//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)
// {
// // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn;
// if (simulationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn))
// {
// return false;
// }
// }
/// <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("- -");
// // The move is valid and legal; update board state.
// if (move.PieceFromHand.HasValue) PlaceFromHand(move);
// else PlaceFromBoard(move);
// return true;
//}
// 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();
}
}
}