Skip to content

Commit

Permalink
This commit makes a few refactorings and adds the '??' command. This …
Browse files Browse the repository at this point in the history
…is the revision of minimal chess that was shown in the 4th video of the Making Of series.
  • Loading branch information
lithander committed Jan 23, 2021
1 parent 7650ce3 commit beba13b
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 201 deletions.
133 changes: 52 additions & 81 deletions MinimalChess/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ namespace MinimalChess

public class Board
{
static readonly int BlackKingSquare = Notation.ToSquareIndex("e8");
static readonly int WhiteKingSquare = Notation.ToSquareIndex("e1");
static readonly int BlackQueensideRookSquare = Notation.ToSquareIndex("a8");
static readonly int BlackKingsideRookSquare = Notation.ToSquareIndex("h8");
static readonly int WhiteQueensideRookSquare = Notation.ToSquareIndex("a1");
static readonly int WhiteKingsideRookSquare = Notation.ToSquareIndex("h1");

public const string STARTING_POS_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

[Flags]
enum CastlingRights
{
Expand All @@ -34,16 +43,14 @@ enum CastlingRights
}

/*** STATE DATA ***/
Piece[] _state = new Piece[64];
CastlingRights _castlingRights = CastlingRights.All;
Color _activeColor = Color.White;
int _enPassantSquare = -1;
private Piece[] _state = new Piece[64];
private CastlingRights _castlingRights = CastlingRights.All;
private Color _activeColor = Color.White;
private int _enPassantSquare = -1;
/*** STATE DATA ***/

public Color ActiveColor => _activeColor;

public const string STARTING_POS_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

public Board() { }

public Board(string fen)
Expand Down Expand Up @@ -136,11 +143,15 @@ public void SetupPosition(string fen)
}
}

//*****************
//** PLAY MOVES ***
//*****************

public void Play(Move move)
{
Piece movingPiece = _state[move.FromIndex];
if (move.Promotion != Piece.None)
movingPiece = Pieces.GetPiece(Pieces.GetType(move.Promotion), _activeColor);
movingPiece = move.Promotion.OfColor(_activeColor);

//move the correct piece to the target square
_state[move.ToIndex] = movingPiece;
Expand Down Expand Up @@ -172,13 +183,6 @@ public void Play(Move move)
_activeColor = Pieces.Flip(_activeColor);
}

static readonly int BlackKingSquare = Notation.ToSquareIndex("e8");
static readonly int WhiteKingSquare = Notation.ToSquareIndex("e1");
static readonly int BlackQueensideRookSquare = Notation.ToSquareIndex("a8");
static readonly int BlackKingsideRookSquare = Notation.ToSquareIndex("h8");
static readonly int WhiteQueensideRookSquare = Notation.ToSquareIndex("a1");
static readonly int WhiteKingsideRookSquare = Notation.ToSquareIndex("h1");

private void UpdateCastlingRights(int squareIndex)
{
//any move from or to king or rook squares will effect castling right
Expand Down Expand Up @@ -221,11 +225,10 @@ private bool IsEnPassant(Piece movingPiece, Move move, out int captureIndex)
captureIndex = Down(_enPassantSquare);
return true;
}
else //not en passant
{
captureIndex = -1;
return false;
}

//not en passant
captureIndex = -1;
return false;
}

private bool IsCastling(Piece moving, Move move, out Move rookMove)
Expand All @@ -235,33 +238,31 @@ private bool IsCastling(Piece moving, Move move, out Move rookMove)
rookMove = Move.BlackCastlingLongRook;
return true;
}
else if(moving == Piece.BlackKing && move == Move.BlackCastlingShort)
if(moving == Piece.BlackKing && move == Move.BlackCastlingShort)
{
rookMove = Move.BlackCastlingShortRook;
return true;
}
else if (moving == Piece.WhiteKing && move == Move.WhiteCastlingLong)
if (moving == Piece.WhiteKing && move == Move.WhiteCastlingLong)
{
rookMove = Move.WhiteCastlingLongRook;
return true;
}
else if (moving == Piece.WhiteKing && move == Move.WhiteCastlingShort)
if (moving == Piece.WhiteKing && move == Move.WhiteCastlingShort)
{
rookMove = Move.WhiteCastlingShortRook;
return true;
}
else //not castling
{
rookMove = default;
return false;
}

//not castling
rookMove = default;
return false;
}

//**********************
//** MOVE GENERATION ***
//**********************


public void CollectMoves(IMovesVisitor visitor)
{
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
Expand Down Expand Up @@ -314,86 +315,57 @@ private void AddMoves(IMovesVisitor moves, int squareIndex)

public bool IsChecked(Color color)
{
//TODO: searching for the king takes time, maybe the bord state could store it
Piece king = Pieces.GetPiece(PieceType.King, color);
Piece king = Pieces.King(color);
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
if (_state[squareIndex] == king)
{
Color enemyColor = Pieces.Flip(color);
return IsSquareAttacked(squareIndex, enemyColor);
}
return IsAttacked(squareIndex, Pieces.Flip(color));

throw new Exception($"Board state is missing a {king}!");
}

private bool IsSquareAttacked(int index, Color enemyColor)
private bool IsAttacked(int index, Color color)
{
int rank = Rank(index);
int file = File(index);
//Square could be threatened by...

//1. Pawns
if (enemyColor == Color.White)
{
//white pawns move up so king is threatened from below, only
foreach (int target in Attacks.BlackPawn[index])
if (_state[target] == Piece.WhitePawn)
return true;
}
else if (enemyColor == Color.Black)
{
//black pawns move down so white king is threatened from above, only
foreach (int target in Attacks.WhitePawn[index])
if (_state[target] == Piece.BlackPawn)
return true;
}
else
return false; //Because it's not a King's square, duh
//1. Pawns? (if attacker is white, pawns move up and the square is attacked from below. squares below == Attacks.BlackPawn)
var pawnAttacks = color == Color.White ? Attacks.BlackPawn : Attacks.WhitePawn;
foreach (int target in pawnAttacks[index])
if (_state[target] == Pieces.Pawn(color))
return true;

//2. Knight
Piece knight = Pieces.GetPiece(PieceType.Knight, enemyColor);
foreach (int target in Attacks.Knight[index])
if (_state[target] == knight)
if (_state[target] == Pieces.Knight(color))
return true;

//3. King
Piece king = Pieces.GetPiece(PieceType.King, enemyColor);
foreach (int target in Attacks.King[index])
if (_state[target] == king)
if (_state[target] == Pieces.King(color))
return true;

Piece queen = Pieces.GetPiece(PieceType.Queen, enemyColor);
Piece bishop = Pieces.GetPiece(PieceType.Bishop, enemyColor);
Piece rook = Pieces.GetPiece(PieceType.Rook, enemyColor);
//4. Queen or Bishops on diagonals lines
for (int dir = 0; dir < 4; dir++)
{
//4. Queen or Bishops on diagonals lines
foreach (int target in Attacks.Diagonal[index, dir])
{
Piece piece = _state[target];
if (piece == bishop || piece == queen)
if (_state[target] == Pieces.Bishop(color) || _state[target] == Pieces.Queen(color))
return true;
if (piece != Piece.None)
if (_state[target] != Piece.None)
break;
}

//5. Queen or Rook on diagonals lines
//5. Queen or Rook on straight lines
for (int dir = 0; dir < 4; dir++)
foreach (int target in Attacks.Straight[index, dir])
{
Piece piece = _state[target];
if (piece == rook || piece == queen)
if (_state[target] == Pieces.Rook(color) || _state[target] == Pieces.Queen(color))
return true;
if (piece != Piece.None)
if (_state[target] != Piece.None)
break;
}
}

//...else
return false;
return false; //not threatened by anyone!
}

//****************
//** KING OVES ***
//** KING MOVES ***
//****************

private void AddKingMoves(IMovesVisitor moves, int index)
Expand Down Expand Up @@ -425,8 +397,8 @@ private void AddBlackCastlingMoves(IMovesVisitor moves)

private bool CanCastle(int kingSquare, int rookSquare, Color color)
{
Debug.Assert(_state[kingSquare] == Pieces.GetPiece(PieceType.King, color), "CanCastle shouldn't be called if castling right has been lost!");
Debug.Assert(_state[rookSquare] == Pieces.GetPiece(PieceType.Rook, color), "CanCastle shouldn't be called if castling right has been lost!");
Debug.Assert(_state[kingSquare] == Pieces.King(color), "CanCastle shouldn't be called if castling right has been lost!");
Debug.Assert(_state[rookSquare] == Pieces.Rook(color), "CanCastle shouldn't be called if castling right has been lost!");

Color enemyColor = Pieces.Flip(color);
int gap = Math.Abs(rookSquare - kingSquare) - 1;
Expand All @@ -439,7 +411,7 @@ private bool CanCastle(int kingSquare, int rookSquare, Color color)

//the king must not start, end or pass through a square that is attacked by an enemy piece. (but the rook and the square next to the rook on queenside may be attacked)
for(int i = 0; i < 3; i++)
if (IsSquareAttacked(kingSquare + i * dir, enemyColor))
if (IsAttacked(kingSquare + i * dir, enemyColor))
return false;

return true;
Expand Down Expand Up @@ -541,7 +513,7 @@ private void AddBlackPawnAttacks(IMovesVisitor moves, int index)

private void AddBlackPawnMove(IMovesVisitor moves, int from, int to)
{
if(Rank(to) == 0) ///Promotion?
if(Rank(to) == 0) //Promotion?
{
moves.Consider(from, to, Piece.BlackQueen);
moves.Consider(from, to, Piece.BlackRook);
Expand Down Expand Up @@ -569,7 +541,6 @@ private void AddWhitePawnMove(IMovesVisitor moves, int from, int to)
//** Utility ***
//**************
private int Rank(int index) => index / 8;
private int File(int index) => index % 8;
private int Up(int index, int steps = 1) => index + steps * 8;
private int Down(int index, int steps = 1) => index - steps * 8;

Expand Down
2 changes: 0 additions & 2 deletions MinimalChess/Evaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ namespace MinimalChess
{
public static class Evaluation
{
public static int MaxValue => short.MaxValue;
public static int MinValue => short.MinValue;
public static int DrawValue => 0;

public static int[] PieceValues = new int[13]
{
Expand Down
86 changes: 30 additions & 56 deletions MinimalChess/Notation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,42 @@ public static class Notation
{
public static char ToChar(Piece piece)
{
switch (piece)
return piece switch
{
case Piece.WhitePawn:
return 'P';
case Piece.WhiteKnight:
return 'N';
case Piece.WhiteBishop:
return 'B';
case Piece.WhiteRook:
return 'R';
case Piece.WhiteQueen:
return 'Q';
case Piece.WhiteKing:
return 'K';
case Piece.BlackPawn:
return 'p';
case Piece.BlackKnight:
return 'n';
case Piece.BlackBishop:
return 'b';
case Piece.BlackRook:
return 'r';
case Piece.BlackQueen:
return 'q';
case Piece.BlackKing:
return 'k';
default:
return ' ';
}
Piece.WhitePawn => 'P',
Piece.WhiteKnight => 'N',
Piece.WhiteBishop => 'B',
Piece.WhiteRook => 'R',
Piece.WhiteQueen => 'Q',
Piece.WhiteKing => 'K',
Piece.BlackPawn => 'p',
Piece.BlackKnight => 'n',
Piece.BlackBishop => 'b',
Piece.BlackRook => 'r',
Piece.BlackQueen => 'q',
Piece.BlackKing => 'k',
_ => ' ',
};
}

public static Piece ToPiece(char ascii)
{
switch (ascii)
return ascii switch
{
case 'P':
return Piece.WhitePawn;
case 'N':
return Piece.WhiteKnight;
case 'B':
return Piece.WhiteBishop;
case 'R':
return Piece.WhiteRook;
case 'Q':
return Piece.WhiteQueen;
case 'K':
return Piece.WhiteKing;
case 'p':
return Piece.BlackPawn;
case 'n':
return Piece.BlackKnight;
case 'b':
return Piece.BlackBishop;
case 'r':
return Piece.BlackRook;
case 'q':
return Piece.BlackQueen;
case 'k':
return Piece.BlackKing;
default:
throw new ArgumentException($"Piece character {ascii} not supported.");
}
'P' => Piece.WhitePawn,
'N' => Piece.WhiteKnight,
'B' => Piece.WhiteBishop,
'R' => Piece.WhiteRook,
'Q' => Piece.WhiteQueen,
'K' => Piece.WhiteKing,
'p' => Piece.BlackPawn,
'n' => Piece.BlackKnight,
'b' => Piece.BlackBishop,
'r' => Piece.BlackRook,
'q' => Piece.BlackQueen,
'k' => Piece.BlackKing,
_ => throw new ArgumentException($"Piece character {ascii} not supported."),
};
}

public static string ToSquareName(byte squareIndex)
Expand Down
Loading

0 comments on commit beba13b

Please sign in to comment.