Thursday, February 20, 2014

A Chess Project, Part 5

Intro

We're on Part 5 now of this large project, and it's time for a little reflection of what's been done as well as a summary of what's left. We've covered the requirements of the project, basic project design, we created the backend web service (WebAPI), we created a sample web-based front-end to exercise the web service, and we have performed request data validation. We're well on the way to stardom! What do we have left? We need to calculate the valid moves that each piece has, we need to discover if any victory or draw conditions have been met, and we will need to do the hardest part so far...write some cheesy AI to figure out a "good" move! We'll crack open this post with valid move calculation. We can't do victory conditions without valid move calculations and vice versa, but we gotta start somewhere so let's calculate basic move actions and we'll put off victory conditions for another time. Onward and forward and such and stuff!

Basic Move Calculation

First step: we need somewhere to put these calculations. You might think that ChessBoard, or even ChessGame are a good place for this, and you'd be right. But hey so am I, I'm going to make a new class for it. I like "The Offspring", so we're gonna keep 'em separated! Go ahead and add a new class called ChessValidMoveCalculator to the BlogChess.Backend project.

This new class will need access to a ChessGame object. Otherwise how will it know of a game and board(s) for which to calculate moves? We'll give it a single private member of type ChessGame as well as a constructor where the caller can pass in a game. Here's the code:

using System;
using System.Collections.Generic;
using System.Linq;

namespace BlogChess.Backend
{
    public class ChessValidMoveCalculator
    {
        private ChessGame m_game;

        public ChessValidMoveCalculator(ChessGame game)
        {
            if (game == null)
                throw new ArgumentNullException("game", "game cannot be null");
            if (game.Positions == null || game.Positions.Count() == 0)
                throw new ArgumentException("game must contain some positions", "game");
            m_game = game;
        }
    }
}



Short and sweet, the code above has just what I said it would have. Note that in the constructor we make sure that the game isn't null and that it has some positions, otherwise there's not much point in trying to calculate moves; there wouldn't be any!

Now it's time to calculate valid moves. I think that sounds like a good method name, so add a method to this class called CalculateValidMoves. I think we're best off with having a return type that is a list of ChessBoards, so give it a return type of IList<ChessBoard>. The method needs no parameters. You should have something like this:

        public IList<ChessBoard> CalculateValidMoves()
        {
            IList<ChessBoard> result = new List<ChessBoard>();
            return result;
        }



I went ahead and created the result variable and returned it, just so the code would compile. Go ahead and try to compile yours too before we get too far.

We're getting closer to the fun part here folks! In my convoluted meat-based thinkin-tool, I believe it would be best to loop through all the squares on the board and see what's there. Then we can base our calculations on what's on the board. So, we need some sort of looping structure in the code that checks what's on what square. We'll need to do this for the last board position of the game, as we don't really care what the valid moves were in the past; we only care right now! Give it a shot yourself first (just create the basic loop structure), then drop on back here to see what I've got:

        
public IList<ChessBoard> CalculateValidMoves()
        {
            IList<ChessBoard> result = new List<ChessBoard>();
            var currentBoard = m_game.Positions.Last().Board;
            var currentStatus = m_game.GameStatus;
            if (currentStatus == GameStatus.BlacksTurn || currentStatus == GameStatus.WhitesTurn)
            {
                //calculate and return moves
                for (short row = 0; row < 8; row++)
                {
                    for (short col = 0; col < 8; col++)
                    {
                    }
                }
            }
            return result;
        }


The next step is determining what type of piece we're looking at. Each piece has its own method of movement which I will assume you know or will look up. But, we need to know the type of piece to know the type of movement, so add that to the loop. Here's my attempt:

                    for (int col = 0; col < 8; col++)
                    {
                        var piece = currentBoard[row, col];
                        switch (piece)
                        {
                            case Constants.Pawn:
                                break;
                            case Constants.Knight:
                                break;
                            case Constants.Bishop:
                                break;
                            case Constants.Rook:
                                break;
                            case Constants.Queen:
                                break;
                            case Constants.King:
                                break;
                        }
                    }


And now our first attempts at determining moves. We're going to start simple here, and pretend there are no other pieces on the board. With that in mind, what can a pawn do? It's got a few options:
  1. Move forward 1 square 
  2. Move forward 2 squares, if haven't moved before
  3. Take a piece forward and left/right
  4. Take another pawn "en passant". This is a weird rule and I might not even both with it in the blog, but hey I have to mention it.
  5. Turn into any type of chess piece when reaching the back rank.
Starting with part 1, how do we code that? First we need to know what color we're looking at. Forward means a different direction for black than it does for white. Picture the board like this:

    col
row  0 1 2 3 4 5 6 7
     1
     2
     3
     4
     5
     6
     7


With the assumption of white starting on rows 6 and 7 (it's 0-based; if we were using 1-based it would be rows 7 and 8) then forward means a decrease in the row. For black starting on rows 0 and 1, forward means an increase in the row. Let's go ahead and code moves 1 and 2 (forward 1 square, forward 2 squares).

                        var piece = currentBoard[row, col];
                        short newRow;
                        short newCol;
                        switch (piece)
                        {
                            case Constants.Pawn:
                                //1 square forward
                                short rowModifier = currentStatus == GameStatus.BlacksTurn ? Constants.BlackTransform : Constants.WhiteTransform;
                                short rowMovementAmount = (short)(1 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = col;
                                if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8)
                                {
                                    var futureBoard = (short[,])currentBoard.Clone();
                                    futureBoard[row, col] = Constants.Empty;
                                    futureBoard[newRow, newCol] = piece;
                                    var newBoard = new ChessBoard(true);
                                    newBoard.Board = futureBoard;
                                    result.Add(new ChessBoard(true));
                                }
                                //2 squares forward
                                rowMovementAmount = (short)(2 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = col;
                                if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8)
                                {
                                    var futureBoard = (short[,])currentBoard.Clone();
                                    futureBoard[row, col] = Constants.Empty;
                                    futureBoard[newRow, newCol] = piece;
                                    var newBoard = new ChessBoard(true);
                                    newBoard.Board = futureBoard;
                                    result.Add(new ChessBoard(true));
                                }
                                break;
                            case Constants.Knight:
                                break;
                            case Constants.Bishop:
                                break;
                            case Constants.Rook:
                                break;
                            case Constants.Queen:
                                break;
                            case Constants.King:
                                break;
                        }


As you can see, the code is getting stringier. I've got a bit of nearly-duplicated code, but maybe we'll refactor that later. For now you can see that I've got 2 new local variables, newRow and newCol. We'll use these to put our movements in. In the Pawn section of our case statement we've got some logic for 1-square forward movement and 2-square forward movement. We first determine whose turn it is, then we move the piece forward a square. We then check to see if the new square is within the bounds of our board, and if so we add the new position to our result set. I can already tell that the duplicated code is going to bug the crap outta me, and we're going to need this same code for the other pieces' valid moves too, so let's clean this up:

        protected IList<ChessBoard> AddMoveToList(IList<ChessBoard> boards, short oldRow, short oldCol, short newRow, short newCol)
        {
            var resultArray = new ChessBoard[boards.Count];
            boards.CopyTo(resultArray, 0);
            var result = resultArray.ToList();
            if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8)
            {
                var futureBoard = (short[,])boards.Last().Board.Clone();
                var piece = futureBoard[oldRow, oldCol];
                futureBoard[oldRow, oldCol] = Constants.Empty;
                futureBoard[newRow, newCol] = piece;
                var newBoard = new ChessBoard(true);
                newBoard.Board = futureBoard;
                result.Add(new ChessBoard(true));
            }
            return result;
        }


This is our new method that adds a new board to a list of boards. Nothing fancy. We just took some of the code from CalculateValidMoves and pushed it in here, since we're going to need it many times. Now this is what you have left in CalculateValidMoves:

        public IList<ChessBoard> CalculateValidMoves()
        {
            IList<ChessBoard> result = new List<ChessBoard>();
            var currentBoard = m_game.Positions.Last().Board;
            var currentStatus = m_game.GameStatus;
            if (currentStatus == GameStatus.BlacksTurn || currentStatus == GameStatus.WhitesTurn)
            {
                //calculate and return moves
                for (short row = 0; row < 8; row++)
                {
                    for (short col = 0; col < 8; col++)
                    {
                        var piece = currentBoard[row, col];
                        short newRow;
                        short newCol;
                        switch (piece)
                        {
                            case Constants.Pawn:
                                //1 square forward
                                short rowModifier = currentStatus == GameStatus.BlacksTurn ? Constants.BlackTransform : Constants.WhiteTransform;
                                short rowMovementAmount = (short)(1 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = col;
                                result = AddMoveToList(result, row, col, newRow, newCol);
                                //2 squares forward
                                rowMovementAmount = (short)(2 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = col;
                                result = AddMoveToList(result, row, col, newRow, newCol);
                                break;
                            case Constants.Knight:
                                break;
                            case Constants.Bishop:
                                break;
                            case Constants.Rook:
                                break;
                            case Constants.Queen:
                                break;
                            case Constants.King:
                                break;
                        }
                    }
                }
            }
            return result;
        }


Looks a little cleaner neh? We replaced the duplicated code with a couple calls to AddMoveToList. Now we need to add in the forward-left and forward-right diagonals. I won't bore you with too much detail as you're getting the hang of it now, so here's the code:

                                //forward-left diagonal
                                rowMovementAmount = (short)(1 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = (short)(col - 1);
                                result = AddMoveToList(result, row, col, newRow, newCol);
                                //forward-right diagonal
                                rowMovementAmount = (short)(1 * rowModifier);
                                newRow = (short)(row + rowMovementAmount);
                                newCol = (short)(col + 1);
                                result = AddMoveToList(result, row, col, newRow, newCol);



This is all getting pretty easy huh? Well unfortunately as usual, I've taken up enough of your time without getting terribly far into the code. It's a lot of code and I guess I'm just a little too wordy!

I can't thank you all enough for reading this far, especially if you started from post #1. I really appreciate it folks. Hang in there through a few more posts and we'll have us a working chess api web service!

What's Next?

Next week we'll have to do some more move calculations. I won't go nearly as much into the mechanics of the movement of pieces or the code of it next week, at least not for the basic movements. That horse is already quite dead. I'll probably just push the basic code up here for the rest of the pieces and then we can move on to discussing and coding the funky movements (promoting pawns, taking pieces, castling, etc). I also think I'll end up cleaning up the code even more next week, as I think CalculateValidMoves is going to get a little unwieldy. We'll see how it goes first, but I bet we'll end up putting each individual piece type's calculation into its own method.

If you'd like to skip ahead of the class, try it out yourself! The basic movements are pretty easy for most pieces and I bet you can all get the code working yourself for the remaining pieces if you have the time. Heck even pawn movement #5 (which we didn't cover) isn't that difficult, just remember that the pawn can turn into whatever it wants to (other than a king) when it gets to the back rank!

Resources

No  special resources used this week. Have fun coding!

No comments:

Post a Comment