Thursday, March 6, 2014

A Chess Project, Part 7

Intro

Here we are in part 7 of the chess project. Last week we coded most of the valid moves available to our chess pieces. This week we'll tackle the remainder. What's left? En passant pawn captures and castling.


En Passant

I'm going to try not to plagiarize here, but I'm sure quite a bit has been written about en-passant so if you recognize these words, sorry. An en-passant capture is a pawn move where it captures an enemy pawn that has just moved two squares forward, pretending the target pawn has moved only a single square forward. The capturer must be on it's color's 5th rank. It's kind of hard to picture so let me refer you to this Wikipedia article which has some nice pictures.

Open up ChessValidMoveCalculator. Skip down to CalculatePawnValidMoves, as this is where we'll be doing our work. We already have calculations in there for moving forward-left and forward-right, and an en passant capture is a special case of these 2 moves. I won't bore you with too much detail, nor will I claim that this code is 100% perfect (darn my lack of unit testing!). Below you will see the modified portion ChessValidMoveCalculator.

                //forward-left diagonal (only if capturing opponent's piece)
                rowMovementAmount = (short)(1 * rowModifier);
                newRow = (short)(currentRow + rowMovementAmount);
                newCol = (short)(currentCol - 1);
                if (newRow >= 0 && newRow <= 7 && newCol >= 0 && newCol <= 7)
                {
                    if (Math.Sign(m_game.Positions.Last().Board[newRow, newCol]) == -Math.Sign(m_game.Positions.Last().Board[currentRow, currentCol]))
                    {
                        if (newRow == 0 || newRow == 7) //back rank promotions
                        {
                            for (short promotionPieceIndex = Constants.Knight; promotionPieceIndex <= Constants.Queen; promotionPieceIndex++)
                            {
                                short promotionPiece = (short)(promotionPieceIndex * rowModifier);
                                boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                            }
                        }
                        else
                            boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                    }
                    else if (m_game.Positions.Count() > 1) //big chunk of logic to see if en passant is possible
                    {
                        if ((currentRow == 3 && currentStatus == GameStatus.WhitesTurn) || (currentRow == 4 && currentStatus == GameStatus.BlacksTurn))
                        {
                            var priorPosition = m_game.Positions.ToArray()[m_game.Positions.Count() - 1].Board;
                            //pawn of opposite color moved up 2 squares next to the current piece
                            if (priorPosition[newRow + rowModifier, newCol] == Constants.Pawn * rowModifier && m_game.Positions.Last().Board[currentRow, newCol] == Constants.Pawn * rowModifier) 
                            {
                                boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                            }
                        }
                    }
                }
                //forward-right diagonal (only if capturing opponent's piece)
                rowMovementAmount = (short)(1 * rowModifier);
                newRow = (short)(currentRow + rowMovementAmount);
                newCol = (short)(currentCol + 1);
                if (newRow >= 0 && newRow <= 7 && newCol >= 0 && newCol <= 7)
                {
                    if (Math.Sign(m_game.Positions.Last().Board[newRow, newCol]) == -Math.Sign(m_game.Positions.Last().Board[currentRow, currentCol]))
                    {
                        if (newRow == 0 || newRow == 7) //back rank promotions
                        {
                            for (short promotionPieceIndex = Constants.Knight; promotionPieceIndex <= Constants.Queen; promotionPieceIndex++)
                            {
                                short promotionPiece = (short)(promotionPieceIndex * rowModifier);
                                boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                            }
                        }
                        else
                            boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                    }
                    else if (m_game.Positions.Count() > 1) //big chunk of logic to see if en passant is possible
                    {
                        if ((currentRow == 3 && currentStatus == GameStatus.WhitesTurn) || (currentRow == 4 && currentStatus == GameStatus.BlacksTurn))
                        {
                            var priorPosition = m_game.Positions.ToArray()[m_game.Positions.Count() - 1].Board;
                            //pawn of opposite color moved up 2 squares next to the current piece
                            if (priorPosition[newRow + rowModifier, newCol] == Constants.Pawn * rowModifier && m_game.Positions.Last().Board[currentRow, newCol] == Constants.Pawn * rowModifier)
                            {
                                boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                            }
                        }
                    }
                }


There are 2 additions, and both are else chunks that have a comment with "en passant" in it. If the current piece is a pawn, and the pawn is on the 5th rank for it's appropriate color, and if the prior move saw a pawn move 2 squares to get next to the current pawn, then en passant is available.


Castling

Castling is an important defensive maneuver in chess whereby the king gets to move 2 squares towards the edge of the board and the rook to that same side jumps 1 square to the opposite side of the king. In the early part of the game this has 2 important purposes: the first part is this gets the king nestled into the easily-defensible corner, usually behind a shield of protective pawns. This gives the king disposable cover that is tough to break through cheaply. The second purpose is it brings the rook closer into play. Rooks are more valuable in the center of the board where they have more room to roam.

 The king can castle in either direction (left and right), but with these limitations:
  1. there can be no other pieces between the king and the rook.
  2. the king and rook must still be on their starting square (they cannot have moved yet during the game).
  3. None of the opponents pieces may threaten squares that the king has to move through, to, or from.
  4. And of course you can't make any move that puts your king in check, including castling.

As you've guessed, the logic for this maneuver will go into the method CalculateKingValidMoves. As you can see below, I've split the castling logic out into 2 sections: 1 for black and 1 for white. It just looks a little cleaner this way:

        protected IList<ChessBoard> CalculateKingValidMoves(short currentRow, short currentCol, IList<ChessBoard> boards, GameStatus currentStatus)
        {
            short newRow;
            short newCol;
            short rowMovementAmount;
            short colMovementAmount;
            bool isWhitePiece = m_game.Positions.Last().Board[currentRow, currentCol] > 0;
            bool isBlackPiece = m_game.Positions.Last().Board[currentRow, currentCol] < 0;
            if ((currentStatus == GameStatus.BlacksTurn && isBlackPiece) || (currentStatus == GameStatus.WhitesTurn && isWhitePiece))
            {
                //+1+1,+1-1,-1+1,-1-1
                for (rowMovementAmount = -1; rowMovementAmount <= 1; rowMovementAmount++)
                {
                    for (colMovementAmount = -1; colMovementAmount <= 1; colMovementAmount++)
                    {
                        newRow = (short)(currentRow + rowMovementAmount);
                        newCol = (short)(currentCol + colMovementAmount);
                        boards = AddMoveToList(m_game.Positions.Last(), boards, currentRow, currentCol, newRow, newCol);
                    }
                }
                //black castle
                if (currentStatus == GameStatus.BlacksTurn && isBlackPiece && currentCol == 4 && currentRow == 0)
                {
                    //castle west
                    if (m_game.Positions.Last().Board[0, 0] == Constants.Rook * Constants.BlackTransform &&
                        m_game.Positions.Last().Board[0, 1] == Constants.Empty &&
                        m_game.Positions.Last().Board[0, 2] == Constants.Empty &&
                        m_game.Positions.Last().Board[0, 3] == Constants.Empty)
                    {
                        var futureBoard = (short[,])m_game.Positions.Last().Board.Clone();
                        futureBoard[0, 2] = Constants.King * Constants.BlackTransform;
                        futureBoard[0, 3] = Constants.Rook * Constants.BlackTransform;
                        boards = boards.ToList();
                        bool anySquareThreatened = false;
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 0);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 1);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 2);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 3);
                        if (!anySquareThreatened)
                            boards.Add(new ChessBoard(true) { Board = futureBoard });
                    }
                    //castle east
                    if (m_game.Positions.Last().Board[0, 7] == Constants.Rook * Constants.BlackTransform &&
                        m_game.Positions.Last().Board[0, 6] == Constants.Empty &&
                        m_game.Positions.Last().Board[0, 5] == Constants.Empty)
                    {
                        var futureBoard = (short[,])m_game.Positions.Last().Board.Clone();
                        futureBoard[0, 6] = Constants.King * Constants.BlackTransform;
                        futureBoard[0, 5] = Constants.Rook * Constants.BlackTransform;
                        boards = boards.ToList();
                        bool anySquareThreatened = false;
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 7);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 6);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 5);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(true, m_game.Positions.Last(), 0, 4);
                        if (!anySquareThreatened)
                            boards.Add(new ChessBoard(true) { Board = futureBoard });
                    }
                }
                //white castle
                if (currentStatus == GameStatus.WhitesTurn && isWhitePiece && currentCol == 4 && currentRow == 7)
                {
                    //castle west
                    if (m_game.Positions.Last().Board[7, 0] == Constants.Rook &&
                        m_game.Positions.Last().Board[7, 1] == Constants.Empty &&
                        m_game.Positions.Last().Board[7, 2] == Constants.Empty &&
                        m_game.Positions.Last().Board[7, 3] == Constants.Empty)
                    {
                        var futureBoard = (short[,])m_game.Positions.Last().Board.Clone();
                        futureBoard[7, 2] = Constants.King;
                        futureBoard[7, 3] = Constants.Rook;
                        boards = boards.ToList();
                        bool anySquareThreatened = false;
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 0);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 1);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 2);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 3);
                        if (!anySquareThreatened)
                            boards.Add(new ChessBoard(true) { Board = futureBoard });
                    }
                    //castle east
                    if (m_game.Positions.Last().Board[7, 7] == Constants.Rook &&
                        m_game.Positions.Last().Board[7, 6] == Constants.Empty &&
                        m_game.Positions.Last().Board[7, 5] == Constants.Empty)
                    {
                        var futureBoard = (short[,])m_game.Positions.Last().Board.Clone();
                        futureBoard[7, 6] = Constants.King;
                        futureBoard[7, 5] = Constants.Rook;
                        boards = boards.ToList();
                        bool anySquareThreatened = false;
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 7);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 6);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 5);
                        anySquareThreatened = anySquareThreatened || ColorThreatensSquare(false, m_game.Positions.Last(), 7, 4);
                        if (!anySquareThreatened)
                            boards.Add(new ChessBoard(true) { Board = futureBoard });
                    }
                }
            }
            return boards;
        }

It's a pretty fair amount of code here; we have to check both directions since the kings and rooks can castle both left and right. We also checked to make sure no pieces are between king and rook, and made sure no opposing pieces threaten the squares they are on or that they will be passing through.

Summary and What's Next

That's it for this week folks. This stuff is getting harder each week! The good news is that there are no more movement types left to code. The bad news is we still have one major restriction to code, and that being that you cannot make a move which puts your own king in check. We will code this one in the next blog post and apply it to all of the move calculations that we have done so far. I'm also becoming more and more wary of how complicated the code is getting; I'm not really trusting the accuracy of the code. We will need to start unit testing this stuff pretty soon, possibly even in the next post. Normally I would do the unit testing alongside the actual coding, but for the sake of keeping these posts reasonable and focused I have foregone unit testing to date.

And finally, here is the code

Resources

Castling

No comments:

Post a Comment