Check the checkmate. Welcome to another problem to solve! If you like playing chess and coding: this oneās for you! Today, we will help the king to survive another day on the battlefield, and also write quite a lot of code.
So without further ado, letās see our problem. Before starting though, as always, two disclaimers:
- These problems are provided by the wonderful newsletter Daily Coding Problem, which you can subscribe toĀ
here : check it out and try solving your daily challenge too! - Iām not an expert programmer, just a guy who likes to share his attempts (and failures). If you have a better idea or solution you are more than welcome to share it: Iād love to see you tackle it!
The problem
Todayās problem was initially asked by Oracle.
You are presented with anĀ
8
Ā byĀ8
Ā matrix representing the positions of pieces on a chess board. The only pieces on the board are the black king and various white pieces. Given this matrix, determine whether the king is in check.
For details on how each piece moves, seeĀ
For example, given the following matrix:
...K....
........
.B......
......P.
.......R
..N.....
........
.....Q..
You should returnĀ True
, since the bishop is attacking the king diagonally.
The problem is quite clear, so we wonāt elaborate more about it. We just need some starting specifications though:
- We are on a graph, not on a board:Ā we will consider each point given by the problem as the center of a case on the board. The result is just the same;
- The white king has gone on vacation with other 7 pawns: king and pawns are the most boring of all the pieces and removing them is not going to change our solution that much;
- We need to verify if the king is in check on this very turn, meaning that on the next white turn it will be taken if he does not move;
- Positions are initially given by the game: I will not go into verifying for overlapping pieces or wrong starting positions.
The solution
First of all, a quick recap of pieces and move-sets:
Given the starting position of each piece and his move-set, we can āeasilyāĀ calculate every possible next move of every piece.
And since they are all interested in capturing the king as quick as possible, we can assume thatĀ their next move will be the one capturing the black king. Therefore, since we also know the black king position, weĀ just need to check if the black king position is one of the possible next moves of the other pieces. If it is, during the next white turn the white piece will move there to capture the black king.
The situation looks something like this:
In the picture above you can see (⦠I hope so, sorry for the confusion ā¦)Ā every possible move of each pieceĀ on the white side, colored with different colors, and the black king above waiting to be captured. So for example the bishop, starting in (2,7), can move to (1,6), (1,8), (3,8), (3,6), (4,5), (5,4) and so on.
I just randomly decided the starting positions of these pieces since this does not affect the final result.
Since we are on a 8x8 graph (to be clear, any other dimensions will work the same way) and we have the starting coordinates of each piece, we can build series of coordinatesĀ x,yĀ which will be our pieces next moves. To do this, we first define aĀ class for each piece,Ā define its coordinates and thenĀ define the rules to calculate every possible move from there.
The pieces ā The Pawn
Letās start simple with the Pawn. By the way, Iām building this inĀ Python, since itās one of the most popular languages at the moment and arguably the most readable by anyone. Still, you will need to know what a class isĀ to be able to follow from now on.Ā
https://gist.github.com/NicolaM94/ae4e0e850250a6974042b0ac72be7585?embedable=true#file-pawn-py
Letās briefly explain: we first define theĀ class Pawn
Ā class andĀ __init__
Ā its coordinatesĀ x,y
Ā . After that, we define theĀ possibleMoves
Ā method to calculate the pawn possible moves from there. The pawn moves are relatively easy, so we simply add them to the variableĀ moves
Ā , after checking that it does not move outside our grid, and return theĀ moves
Ā array. Two things to notice here, specifically for the pawn:
-
We consider the white pawn to move from bottom to top, like if we are the white player moving our pawn ahead towards our opponent. This does not really change anything, itās just keeping things in order.
-
WeĀ intentionally skip the normal movement, since we are interested in capturing the black king: since the pawn can only capture diagonally and we donāt care about moving the pieces in other directions, we can skip its normal movement.
Now we can simply check the pawnās moves by giving him its coordinates and calling theĀ possibleMoves
Ā method, like this:Ā print(Pawn(7,2).possibleMoves())
Ā and we should obtain something likeĀ [(6,3),(8,3)]
Ā .
Also, you can see at the top thatĀ we defined our grid dimensions as variables, so we can possibly change them to run the algorithm on boards of other dimensions.
Now letā s move on to the tower.
The tower
https://gist.github.com/NicolaM94/7897939bdd7933cb09d8915b7b8bc0b5?embedable=true#file-tower-py
Again, we init the Tower class with its coordinates and define the functionĀ possibleMoves
Ā . To collect all the possible moves hereĀ we range all the points on the tower two axes and add each single coordinate to the variableĀ moves
Ā , which will be returned after all the loops. As before, to check the tower moves we can simplyĀ print(Tower(5,3).possibleMoves())
Ā and we should get something like this:Ā [(1,3), (2,3), (3,3), ..., (5,8)]
Ā .
The bishop
Now itās time for the bishop.
https://gist.github.com/NicolaM94/a6ae6f6138b78d550b2f2e27d5c453aa?embedable=true#file-bishop-py
Bishopās moves are more complex than the tower, so we might explain a bit whatās happening here. BasicallyĀ we need to collect points on the two diagonal axes starting from its starting position. After declaring the class and the init method, we create two variables:Ā moves
Ā , as before, andĀ currentPos
Ā , which will be used to keep track of the points we collected while moving along its axes. Now usingĀ while
Ā loop, and checking that we are not moving outside our grid, weĀ increase and decreaseĀ x
Ā andĀ y
Ā accordingly to where we want to move. For example, if we want to move up-left from its starting positions, we will need to decrement theĀ x
Ā value while incrementing theĀ y
Ā value. If we want to move down right, we will need to increment theĀ x
Ā value while decrementing theĀ y
Ā value and so on. Finally, we simply returnĀ moves
Ā once again.
The queen
Now you could think: if tower moves are complex and bishop are even more, how the heck are we going to code the queen? Actually, the queen moves are the most easy to code, just with the help of a simple trick. Letās see the queen then.
https://gist.github.com/NicolaM94/22bd55aab7a0976f4656403c3749a6ee?embedable=true#file-queen-py
Alright, whatās happening here? Well, if we think about it,Ā the queen has the same moves of the bishop and the tower combined. Sheās more like a tower shaped bishop mecha-bot than a queen. For this reason, coding her moves is really simple. After declaring her class we simply define herĀ possibleMoves
Ā as theĀ union array of moves resulting from bishopās and towerās possible moves.
Notice that while calling the classesĀ Bishop
Ā andĀ Tower
Ā instances in theĀ possibleMoves
Ā function their parametersĀ x
Ā andĀ y
Ā are given asĀ self.x, self.y
Ā , so they are actually the queens coordinates.
The knight
Now to the knight!
https://gist.github.com/NicolaM94/c19d2c41e4461c66fca54aab239ede9e?embedable=true#file-knight-py
The knight is the most particular to me. His move-set is strange, like āLā shaped, so I did not find a particularly clever or fast way to code it and I ended up hard coding its moves simply calculatingĀ each of the 8 possible moves he has from his starting position.
The king
A couple of brief concepts about the king. Since we are not required to move the king, just giving out if heās checked or not,Ā we donāt really need to implement his move-set. However, we will also see a brief implementation in the end of the article. Also, coding it is not as simple as nerfing down the queenās move-set, as we will see later. So for now, letās skip his movements and see the solution.
The solution code
Given that we have the possible positions for each piece, the solution is pretty simple now: we just need to check if the black king coordinates are in the set of moves of one or more of the other pieces. Letās code it!
https://gist.github.com/NicolaM94/75826a753105c3a3aecae4f6890b631a?embedable=true#file-check-py
We now simply create a variableĀ blackKing
Ā as a couple of coordinates. Then for each piece we create an instance of its class we just built and call the methodĀ possibleMoves
Ā to calculate its full set of moves.Ā Once we have it, we check if theĀ blackKing
Ā coordinates are present in those move-sets: if they are, we print out that the king is checked by that particular piece. The result here is something like this:
Pawn moves: [(8, 3), (6, 3)]
Tower moves: [(1, 3), (2, 3), (3, 3), (4, 3), (6, 3), (7, 3), (8, 3), (5, 1), (5, 2), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8)]
Check by the tower!
Bishop moves: [(3, 8), (1, 6), (1, 8), (3, 6), (4, 5), (5, 4), (6, 3), (7, 2), (8, 1)]
Queen moves: [(4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (2, 1), (2, 3), (1, 4), (4, 1), (1, 2), (2, 2), (4, 2), (5, 2), (6, 2), (7, 2), (8, 2), (3, 1), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8)]
Knight moves: [(4, 7), (4, 5), (8, 7), (8, 5), (5, 4), (7, 4), (7, 8), (5, 8)]
Check by the knight!
I used the messy image above as reference, so you could check that the king is actually checked by the tower and the knight.
Check mate
What about if we also want to calculateĀ if the king has still some chances to surviveĀ or it actually is check mate? For this purpose, we need to calculate the kingās set of moves.
https://gist.github.com/NicolaM94/cca18293bedb2293bbc621e86236571f?embedable=true#file-king-py
Letās describe it a bit. First off, we define our class and coordinates as before. After that we create theĀ possibleMoves
Ā method and code the possible moves of the king (again, Iām pretty sure that there is a faster way, but there are just 8 possible moves soā¦). After that,Ā we check what moves are illegal (move outside the board)Ā and return only theĀ validMoves
Ā .
So now, to check if the king has still a chance to survive,Ā we need to check if any of his possible moves is not in another piece set of moves.Ā To do this we simply loop over the king moves and the other moves.
https://gist.github.com/NicolaM94/9bdb64c10e2d039905e6353ed37cc101?embedable=true#file-checkmate-py
So thereās still hope for our king to survive! Lucky for him I guessā¦
Time complexity
As always, a brief look at the time complexity of this solution.
First off, since each piece is anĀ instance of a class, we must consider the time to initialize that instance.
- Pieces like theĀ pawn or the knightĀ do not have any loop inside them and do not depend on any variable dimension, so we can consider them O(1);
- TheĀ towerĀ is a bit tricky: since it contains 4 for loop you could instantly think that its time complexity is O(n), right? Actually, if we think about how the tower moves, weāll notice thatĀ no matter where its placed on the board, its moves are limitedĀ to the free board cases on its vertical and horizontal axes. And since the board is a square, we will always have 14 free cases to move our tower on. So its time complexity should actually be O(1), constant to 14 couples of coordinates. Hereās a graphic example:
- Unluckily, we canāt say the same for theĀ bishop, which set of moves appears to be a bit more complex. Basically, it has 7, 9, 11 or 13 moves depending on where it is placed on the board. Therefore, the steps needed to calculate his set of moves are based on his position, thus we may consider a t.c. of O(n).
-
TheĀ queenĀ needs the set of moves of the tower and the bishop combined. SinceĀ while evaluating time complexity we must focus on the worst scenario, the t.c. of the bishop prevails on the t.c. of the tower. Thus, we must consider a t.c. of O(n) also for the queen.
-
At last, theĀ kingĀ has an hard coded set of moves, but also aĀ validation process involved which uses a for loop. So technically, even if the kingās set of moves is relatively small in any case, we must consider his t.c. as O(n), also depending on his position on the board.
At this point, we useĀ a for loop for each piece to verify and print out the check status of the black king. This gives us no problems where the t.c. is constant (take the tower, for example: we calculate its 14 moves and then loop other 14 times over them at most, so we can consider it also fixed time). Still, weāre going to have troubles where the t.c. is O(n) or above, since we are adding a loop which will also grow while the number of moves grow.
Finally, we use a double (inner) for loop to verify the check mate, which is definitely going to be a t.c. of O(n²), depending on the number of moves of the king and the moves of the other pieces.
Thatās it; thereās my solution. Iām well aware that some points are not as most efficient as possible, but while writing I liked the idea of describing the process more than building the perfect solution.
What do you think about this? Give me your opinion about it and letās discuss your solutions in the comments!
As always, if you liked the article, please leave a clap or two and subscribe, or share it with someone who could be interested in this kind of algorithms, if you can! Thanks for reading until the end, this time more than always given the length of this solution!
As always, thanks for reading.