Skip to content

Commit

Permalink
Add a method to convert ChessMove into a Standard Algebraic Notation …
Browse files Browse the repository at this point in the history
…(SAN). Add a button onto Chessboard Tester to undo moves.
  • Loading branch information
DCourtel committed Jan 18, 2021
1 parent e2d0046 commit cb25c7e
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 37 deletions.
106 changes: 104 additions & 2 deletions Chessboard Control/Board/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,10 @@ public ChessMove GetMoveValidity(ChessSquare from, ChessSquare to)
// Checks if making the move will put the King in Check
MovePiece(validationResult);
Turn = SwapColor(Turn);
if (IsKingAttacked(Turn))
var isKingAttached = IsKingAttacked(Turn);
UndoMove();

if (isKingAttached)
{
validationResult.IllegalReason = ChessMoveRejectedReason.PutKingInCheck;
}
Expand All @@ -748,7 +751,6 @@ public ChessMove GetMoveValidity(ChessSquare from, ChessSquare to)
validationResult.IsValid = true;
validationResult.IllegalReason = ChessMoveRejectedReason.None;
}
UndoMove();
}
else
{
Expand Down Expand Up @@ -1013,6 +1015,106 @@ public ChessMove UndoMove()

#region Private Methods

/// <summary>
/// Converts a full <see cref="ChessMove"/> to its Standard Algebraic Notation (SAN).
/// </summary>
/// <param name="move">A valid <see cref="ChessMove"/> to convert to Standard Algebraic Notation (SAN).</param>
/// <returns></returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="move"/> is not a valid move.</exception>
internal string MoveToSAN(ChessMove move)
{
if (!move.IsValid) { throw new ArgumentException("The move is not valid."); }
var kingStatus = string.Empty;

// Check and Checkmate
if (IsCheck)
{
kingStatus = "+";
if (IsCheckmate) { kingStatus = "#"; }
}

// Pawn
if (move.MovingPiece.Kind == ChessPieceKind.Pawn)
{
var promotion = "";
// Promotion without capture
if ((move.MoveKind & ChessMoveType.Promotion) == ChessMoveType.Promotion) { promotion = $"={FEN.ChessPieceKindToFEN(move.PromotedTo).ToUpper()}"; }
if (move.MoveKind == ChessMoveType.Promotion) { return $"{move.To.AlgebraicNotation}{promotion}{kingStatus}"; }
// Move without capturing
if (move.MoveKind == ChessMoveType.Normal || move.MoveKind == ChessMoveType.Big_Pawn) { return $"{move.To.AlgebraicNotation}{kingStatus}"; }
// Capture
if ((move.MoveKind & ChessMoveType.EP_Capture) == ChessMoveType.EP_Capture) { return $"{move.From.File}x{move.To.AlgebraicNotation} e.p.{kingStatus}"; }
return $"{move.From.File}x{move.To.AlgebraicNotation}{promotion}{kingStatus}";
}

// King
if (move.MovingPiece.Kind == ChessPieceKind.King)
{
// Roque
if (move.MoveKind == ChessMoveType.KSide_Castle) { return "O-O"; }
if (move.MoveKind == ChessMoveType.QSide_Castle) { return "O-O-O"; }
// Move without capturing
if (move.MoveKind == ChessMoveType.Normal) { return $"K{move.To.AlgebraicNotation}"; }
// Capture
return $"Kx{move.To.AlgebraicNotation}{kingStatus}";
}

// Sliding pieces
var pieceAbrev = FEN.ChessPieceToFEN(move.MovingPiece).ToUpper();
var disambiguator = GetDisambiguator(move);

return $"{pieceAbrev}{disambiguator}{((move.MoveKind & ChessMoveType.Capture) == ChessMoveType.Capture ? "x" : "")}{move.To.AlgebraicNotation}{kingStatus}";
}

private string GetDisambiguator(ChessMove move)
{
Dictionary<ChessFile, int> matchingFiles = new Dictionary<ChessFile, int>();
Dictionary<ChessRank, int> matchingRanks = new Dictionary<ChessRank, int>();
List<ChessSquare> candidateSquares = GetAllPieces(move.MovingPiece.Kind, Turn, move);

if (candidateSquares.Count < 2) { return string.Empty; }

foreach (ChessSquare square in candidateSquares)
{
if (matchingFiles.ContainsKey(square.File)) { matchingFiles[square.File]++; } else { matchingFiles.Add(square.File, 1); }
if (matchingRanks.ContainsKey(square.Rank)) { matchingRanks[square.Rank]++; } else { matchingRanks.Add(square.Rank, 1); }
}
var cannotUseFile = false;
var cannotUseRank = false;
foreach (var fileCount in matchingFiles.Values)
{
if (fileCount > 1) { cannotUseFile = true; }
}
foreach (var rankCount in matchingRanks.Values)
{
if (rankCount > 1) { cannotUseRank = true; }
}
if (cannotUseFile && cannotUseRank) { return $"{move.From.File}{1 + (int)move.From.Rank}"; }
if (cannotUseFile) { return (1 + (int)move.From.Rank).ToString(); }

return move.From.File.ToString();
}

private List<ChessSquare> GetAllPieces(ChessPieceKind pieceKind, ChessColor color, ChessMove move)
{
List<ChessSquare> candidates = new List<ChessSquare>();

for (int file = 0; file < 8; file++)
{
for (int rank = 0; rank < 8; rank++)
{
var index = 16 * rank + file;
var piece = board[index];
if (piece != null && piece.Kind == pieceKind && piece.Color == color && PieceCanAttack(index, move.To.x88Notation))
{
candidates.Add(new ChessSquare(index));
}
}
}

return candidates;
}

private List<ChessMove> GenerateCastling()
{
List<ChessMove> moves = new List<ChessMove>();
Expand Down
56 changes: 52 additions & 4 deletions Chessboard Control/Board/ChessMove.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ internal ChessMove(ChessSquare from, ChessSquare to)
/// <summary>
/// Gets or sets the type of the piece created during a pawn promotion.
/// </summary>
public ChessPieceKind PromotedTo {
public ChessPieceKind PromotedTo
{
get { return _promotedTo; }
set
{
Expand All @@ -61,7 +62,25 @@ public ChessPieceKind PromotedTo {
throw new System.ArgumentOutOfRangeException("You cannot promote a Pawn to a Pawn or a King.");
}
}
}
}

private string _toSAN = null;
/// <summary>
/// Gets or sets the move expressed in the Standard Algebraic Notation (SAN).
/// </summary>
public string ToSAN
{
get
{
if (_toSAN == null)
{
_toSAN = GetPartialSAN();
}
return _toSAN;
}

internal set { _toSAN = value; }
}

/// <summary>
/// Gets or sets the square where the piece move to.
Expand All @@ -80,16 +99,45 @@ public ChessMove Clone()
clone.To = this.To;
clone.MovingPiece = this.MovingPiece;

return clone;
return clone;
}

#endregion Properties

#region Methods

private string GetPartialSAN()
{
if (MovingPiece.Kind == ChessPieceKind.Pawn)
{
if (CapturedPiece == ChessPieceKind.None)
{
return To.AlgebraicNotation;
}
else
{
if ((MoveKind & ChessMoveType.EP_Capture) == ChessMoveType.EP_Capture)
{
return $"{From.File}x{To.AlgebraicNotation} e.p.";
}
return $"{From.File}x{To.AlgebraicNotation}";
}
}
else
{
if (MoveKind == ChessMoveType.KSide_Castle) { return "O-O"; }
if (MoveKind == ChessMoveType.QSide_Castle) { return "O-O-O"; }
if ((MoveKind & ChessMoveType.Capture) == ChessMoveType.Capture)
{
return $"{FEN.ChessPieceToFEN(MovingPiece).ToUpper()}x{To.AlgebraicNotation}";
}
return $"{FEN.ChessPieceToFEN(MovingPiece).ToUpper()}{To.AlgebraicNotation}";
}
}

public override string ToString()
{
return $"{MovingPiece.Kind} {From}{(CapturedPiece != ChessPieceKind.None ? "x" : " ")}{To} ({(IsValid ? "Legal" : "Illegal")}){(IsValid ? "" : $"({IllegalReason})")}";
return ToSAN;
}

#endregion Methods
Expand Down
8 changes: 6 additions & 2 deletions Chessboard Control/Chessboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,10 @@ public void SetupInitialPosition()
/// <returns>An instance of the last move or null if there is no moves in the MoveHistory.</returns>
public ChessMove UndoMove()
{
return ChessEngine.UndoMove();
var undoneMove = ChessEngine.UndoMove();
Invalidate();

return undoneMove;
}

#endregion Public Methods
Expand Down Expand Up @@ -673,8 +676,9 @@ private void Chessboard_MouseUp(object sender, MouseEventArgs e)
if (!promotionCancelled)
{
MovePiece(moveValidationResult);
moveValidationResult.ToSAN = ChessEngine.MoveToSAN(moveValidationResult); // Update the SAN after promotion

OnPieceMoved?.Invoke(this, moveValidationResult);
OnPieceMoved?.Invoke(this, moveValidationResult);
if (ChessEngine.IsCheckmate)
{ OnCheckmate?.Invoke(this, new EventArgs()); }
else if (ChessEngine.IsCheck)
Expand Down
33 changes: 15 additions & 18 deletions Chessboard Control/FenValidator/FEN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,31 @@ public FEN(string fen)
public static string ChessPieceToFEN(ChessPiece piece)
{
if (piece == null) { throw new ArgumentNullException(nameof(piece)); }
var result = "";
var result = ChessPieceKindToFEN(piece.Kind);

switch (piece.Kind)
return (piece.Color == ChessColor.White ? result.ToUpper() : result.ToLower());
}

public static string ChessPieceKindToFEN(ChessPieceKind kind)
{
switch (kind)
{
case ChessPieceKind.None:
result = "";
break;
return "";
case ChessPieceKind.Pawn:
result = "p";
break;
return "p";
case ChessPieceKind.Knight:
result = "n";
break;
return "n";
case ChessPieceKind.Bishop:
result = "b";
break;
return "b";
case ChessPieceKind.Rook:
result = "r";
break;
return "r";
case ChessPieceKind.Queen:
result = "q";
break;
return "q";
case ChessPieceKind.King:
result = "k";
break;
return "k";
}

return (piece.Color == ChessColor.White ? result.ToUpper() : result.ToLower());
throw new ArgumentException($"Unable to translate {kind} into a FEN abbreviation.");
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions Chessboard Control/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut
// en utilisant '*', comme indiqué ci-dessous :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.2101.17")]
[assembly: AssemblyFileVersion("1.0.2101.17")]
[assembly: AssemblyVersion("1.0.2101.18")]
[assembly: AssemblyFileVersion("1.0.2101.18")]
22 changes: 18 additions & 4 deletions Chessboard Tester/Form1.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Chessboard Tester/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private void chessboard1_OnSquareSelected(Chessboard sender, ChessSquare origin,

private void chessboard1_OnPieceMoved(Chessboard sender, ChessMove move)
{
LblLastMove.Text = $"Last move: {move.MovingPiece.Kind} from {move.From} to {move.To} {(move.CapturedPiece == ChessPieceKind.None ? "No capture" : $"{move.CapturedPiece} was captured")}";
LblLastMove.Text = $"Last move: {move.ToSAN}";
LblGameStatus.Text = "Game status:";
}

Expand Down Expand Up @@ -72,6 +72,11 @@ private void BtnMovePiece_Click(object sender, EventArgs e)
}
}

private void BtnUndoMove_Click(object sender, EventArgs e)
{
chessboard1.UndoMove();
}

private void ChkBxFlipBoard_CheckedChanged(object sender, EventArgs e)
{
chessboard1.FlipBoard();
Expand Down
4 changes: 2 additions & 2 deletions Chessboard Tester/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut
// en utilisant '*', comme indiqué ci-dessous :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.2101.17")]
[assembly: AssemblyFileVersion("1.0.2101.17")]
[assembly: AssemblyVersion("1.0.2101.18")]
[assembly: AssemblyFileVersion("1.0.2101.18")]
Loading

0 comments on commit cb25c7e

Please sign in to comment.