I have finished working out the possible rook moves and created all the tests to make sure it all works correctly.
I now want to do the same thing but for the bishop.
It should be basically the same, but movements are diagonal instead of vertical and horizontal.
The function I need to create is _listBishop, which is very similar to _listRook.
At the start I am setting the player's piece to either white or black bishop (instead of rook).
For each direction I am moving differently, moving to the top left, top right, bottom left and finally the bottom right.
Everything else is the same as the _listRook function.
When things like this are easy, it is a good indicator that you are doing it right. It is when things start to get too complex, unmanageable, messy, that you need to think about redesigning, rewriting, and having another go. I feel good about how this move calculator class is going, though it is still early days with it.
I have created all the testing code too. This is very similar to the testing I did for the rook. Testing what happens when the bishop is in the middle of an empty board, on the edges, in the corners, stopped by its own pieces and able to capture other pieces.
I can now create the _listQueen function in the same way as I did with both _listRook and _listBishop.
However, I am moving in all directions, so instead of 4 direction loops, I will been to perform 8 loops.
I am setting the player piece to either white or black queen. Instead of having 4 directions, I am now using 8 and setting the next row and columns when looping. The rook, bishop and the queen have simple movement rules, so it was easy to write algorithms for them. Things may be more interesting with the remaining pieces.
I have created all the tests. I do now feel that using hard coded tests was the right way to do this, instead of using some sort of testing JSON file. However, I am spending 5 minutes writing code, and an hour writing tests for it. It is not very exciting stuff, and keeping my focus on it can be difficult.
While testing I did see an issue in my design, a bit of an oversight, but nothing too problematic. There is nothing stopping a piece captioning a king. This is something I don't want to happen, but the board should even get into a state where that would be possible. The king would be in check at this point, and the other player would need to make sure it doesn't stay in check.
The knight moves differently and therefore I need to think of a way to check each of the locations it can possibly move to. It moves one square to the left or right and then two squares up or down. It can jump any pieces that are in the way too.
The start of the function sets the player's piece to either white or black knight, just like the other functions. I then create an adjustment list that will be used to change the row and column location of the starting point, so that I can work out the different places the knight could move to.
I then loop through the 8 different adjustments, make the new location adjustment for each one, check the piece is still on the board, and then I perform the same set of steps I have done in the other functions. This does feel like an elegant method of checking the knight moves. Things are moving along nicely.
With testing I needed to look at the following things.
I did find a problem with my code while testing.
I had copied some of the code from the _listRook function, including the lines that checked if the location was outside the board,
which used the break keyword.
I did leave that keyword in, but once the adjusted location was outside the board, it broke the loop and ended up not looking at the other adjustments.
I replaced the break keyword with continue.
It looks like testing is a good idea.
Only two more pieces to go.
The pawn and the king.
The king can move in any direction, like the queen, but only by one space. It can also perform a castle, on either the kings or queen side of the board, but only if the rook and king have not moved, and there is nothing between the two pieces.
I will need to create two functions, _listKingWhite and _listKingBlack, because of the extra checks required for castling.
This is the start of the _listWhiteKing function.
I am using the adjust list method I used with the knight function to check the 8 locations that surround the king.
The next part is like the other functions I have created so far.
I am getting the piece and seeing if it is empty, just like before, but after that I am using the isBlack function to see if I can capture the piece.
Then, if a move object exists, I add it to the list of moves. So far everything is like the functions I created for the other pieces.
But I also need to check for the castling moves.
The first thing I do next is to see if the white king has moved or not. If it has moved, then I cannot perform any castling, on either the king or queen side. Then I check if the king's rook has moved, and then after that I look to see if the queen's rook has moved too. If either of them has not moved then I can continue to perform some other checks to see if the castle can be performed.
The extra checks are to see if the pieces between the king and the rook are in the way or not. If any piece is in between them then it is not possible to perform a castle move. It is possible that the king can be castled on both sides, so you could end up with two possible castle moves in the list.
While writing the testing code I found a problem. Look at this simple test of moving a king when it is in the middle of the board, on its own.
The problem with this is that it finds 8 moves, plus an extra two castle moves that should not be possible. There are no rooks, and the king is not in its starting location, so it has technically moved. So, what went wrong?
When you create a blank board, the whiteKingMoved property is set to false, so are the whiteKingRookMoved and
whiteQueenRookMoved properties.
With these set the way they are, the _listWhiteKing function will think the king hasn't moved, the rook hasn't moved,
and there is nothing in between them to stop a possible castle.
But what do I do about it?
If I think about it, with a blank board, technically the white king hasn't moved, even when one doesn't exist.
So, setting this to false feels correct, and setting it to true doesn't make sense.
I would change the _listWhiteKing function to check the king and the rook exists before moving them.
Also, castling only works for an 8x8 chess board, so I should check for this too?
I have made several changes to better cope with castling. The first check is to only look at castling if the row and column it is looking at is the same as the white king's starting location. Then I get the piece and make sure it is a white king. Then, inside the rook moved blocks, I also check that the rook location contains a white rook.
I needed to add an extra tool function when testing the castling parts. This looks for a castle move object with all the parameters it should contain. I needed to test the following.
I am finding a few bugs in my code while writing out and running the tests. For this type of library, it would be very difficult to spot the bugs using some other method. Running the finished chess game multiple times, manually with real people, to see if there is any odd behaviour, doesn't seem practical.
I have left pawns to last because the rules around them are not straightforward and require more work to handle. Pawns move forward, capture diagonally, can perform an "en passage" move, and can be promoted when they reach the end of the board.
I will need to create a function for the white pawn and another for the black one. As they are moving in the opposite directions the checks will be related to different parts of the board.
Here is the start of the _listWhitePawn function.
I am first checking to see if the pawn is on the starting row, and if so, it then checks if the square above it is empty.
If the space is empty, then the pawn can move there.
After this, I check the square above that, and if that is also empty, then I can move the pawn two spaces up.
This will make sure that I only move the pawn up two spaces if the one above it is empty.
While I was thinking about the pawn being promoted, I realized that you can move a pawn to the top of the board,
but you can also capture a piece and get promoted at the same time.
This last part is something I currently do not take into account.
I have a static Move.createPromotion function but do I need to create a createCapturePromotion function as well,
or can I just add a capture parameter to the first one?
After some thought, I think that adding an extra parameter that contains the piece the pawn took while it is being promoted, which could be set to EMPTY, would be trying to make the capture property do more work than it should be. It feels like solving the problem in a quick and dirty way. Instead, I am going to add a new move type to handle this.
I have added a new move type constant value for the pawn capturing a piece and getting promoted. You'll notice that the value of the en passant constant has changed, it was 5 but is now 6. This is why I like to use constants in this way, so that I can change their values without needing to change any of the places they are used.
The new create function is very similar to the other promotion function, but I include the capture piece property.
I also added some extra testing of the Move class to make sure everything is still working.
I have started adding some testing to make sure the pawn movements are working as I want. But while doing so, I am finding some problems already. I am currently checking the first row, for the pawns starting move, but after that, I will also need to check each pawns move after that. This included the following.
If the pawn is not in a place where it could be promoted, then I check to see if there is a space above the pawn and if there is then I can add a move. However, I just did the same thing when the pawn was on row 1, its starting location. I will end up adding the same move twice. This pawn moving business is starting to look messy. Back to the section that checks if the row is the pawns starting location.
Now I am only checking if the pawn can perform a two step forward move. All the other moves it can make when on row 1, like a single move forward and capturing a piece, will be done later.
The next section I want to look at is when the pawn is on any row except the last but one. When a pawn is on one of these rows it can move forward one row or capture a piece if it is diagonally to it.
I get the piece on the board that is in the same column but up one row. If this is empty, then the pawn can move there.
When checking if the pawn can capture a piece that is up one row and to the left, I first need to make sure I am not on the left edge of the board. I then get the piece from that location and see if the piece is black. If it is then I can capture it.
The same type of steps are then performed when looking to see if the pawn can capture a piece one row up and to the right. I do need to check that the pawn is not on the right most edge of the board first.
I now move on to the last by one row, where I check to see if a pawn can be promoted, either by just moving forwards one row, or by capturing a piece. The row number would need to be the same as the board's row count minus 2 (remember the rows are 0 to 7, so with the row count being 8, I want row 8 - 2 = 6).
Inside I need to make sure the row ahead is empty, or if one of the diagonal squares contains a black piece.
I get the square ahead of the pawn and check to see if it is empty. If it is then I create 4 promotion move objects, one for each piece the pawn can be promoted to. These are then added to the move list.
I now want to see if I can capture a piece that is on the column to the left of the pawn. I first need to check I am not on the left edge of the board. I get the square above and to the left of the pawn and see if it is a black piece or not. Like before, I create 4 capture promotion move objects for the different pieces the pawn can be promoted to. Each of these are then added to the list.
I then do the same thing but for the right side. I first make sure I am not on the right edge of the board, then get the square to the right, check its black, and again create 4 capture promotion move objects, which are then finally added to the move list.
When testing these promotion and capture promotion moves, I had to create some related TestMoveTools.promotionExist and
TestMoveTools.capturePromotionExist functions.
So far, I have tested the following.
I now need to work out how to process the "en passant" move.
This will require the use of the board's enPassantColumn property, which is used to state if the opposition's last move
was a pawn that moved past the pawn I am looking at, and didn't give me a chance to capture it.
I only need to check for the en passant if the property for it is used. If it is used, then I need to check if the pawn is on the row that allows it to perform the en passant. Then I check if the pawn is directly left or right of the pawn. If it is, then I can create the en passant move and add it to the list. There doesn't seem to be too much involved, just making sure the pawn is in the right place for it to be able to perform the en passant move.
Testing this part did require a new TestMoveTools.enPassantExist function, to check the en passant move was created and that it contains the correct values.
I have finished the move function for the white pawn, but now I need to do the same with the black version. This is basically the same but with different row values and some other small changes. I also need to duplicate all the testing but using black pawns instead of white.
After finishing the pawn related functions, I have finally created all the move functions for all the pieces. Before I move on though, I reread the chess rules to make sure I haven't overlooked something. As it happens, it does look like I have missed something. I should not be allowed to castle a king when it is in check. This is something I will need to add, but it leaves me with a question. Do I change the board by adding a new property that keeps the "in check" state, or do I check if the king is in check before seeing if I can castle it?
I start to look at the Board class and think about how it would be used externally by people using the chess library and think about
what they need from it to allow them to achieve their desired goals.
I plan to create a web component that allows someone to select a piece, see where it can possibly move to and then select the move,
which would change the pieces on the board.
I want to give the web component an instance of the board, and have it place all the pieces in their given locations.
However, I want the board to contain some extra information that it currently does not have. It needs to state if the current player is in check. I also want to state if the current player has lost (in checkmate). This will stop the need for the web component to work out if the player is in check or checkmate by using some function. The information is right there, in the board object, no extra work is required.
I now need to think about what to call the new properties. I was thinking about "inCheck" and "inCheckmate", but after some thought I decided to go with "check" and "checkmate".
I also made changes to the clone, toCode and fromCode static functions.
Along with this, I have also updated the testing of the Board class, which is just as well, because there were bugs in the decoding section.
I do get a little worried when I don't find anything wrong with my code.
I only added two properties, but it does require extra work in different places, so I need to make sure I haven't missed any areas that use the Board class.
Fortunately, at this stage of development, the code base is small, so any adjustments can easily be managed.
Now I can go back to the _listWhiteKing and _listBlackKing functions and add an extra condition so that the king cannot castle when
it is in check.
It was a simple one-line adjustment.
If the board check properly is set to true, then the king cannot perform a castle.
This has been done for both functions, and an extra test has been added to check it works.