AI Based Chess Solver

The aim of this project is to create an artificial intelligence that can play chess and analyze how it has improved with the different iterations of the engine. To achieve this goal different approaches to chess engines will be explored. These different algorithms will be implemented into Python with the aim of creating a low to midlevel chess engine


History
In the early years of chess engines, the idea of having a computer that played chess at the same level as the best grandmasters was unimaginable. Since chess requires a lot of creativity and has millions of possibilities, the average number of legal different positions that you can find is around 10^40 and 10^50(Holcomb). Nevertheless, nowadays it has been proven that chess engines are far superior to the best grandmasters. It should be noted that chess engines and humans approach chess in a different way. Fundamentally they both analyze and calculate moves and search ahead to predict how the game would go if they followed that line.
The major difference is, when the best chess player Kasparov can only analyze 3 to 5 positions per second, Deep Blue was analyzing at the time 200 million positions per second (IBM100 -Deep Blue, 2012). Even with this disparity, the games ended up even. Out of the 6 games played between them, Kasparov won 1 tied 3 and lost 2. We can make the conclusion that humans are much more efficient at choosing the correct line to follow. ("IBM100 -Deep Blue," 2012.) The major fight of chess engines is knowing which lines are better to pursue, and it has proven that this is quite an arduous task. Having the computational capacity of an engine and the human intuition would be perfect, but how do humans get that game sense and how can we recreate it on a chess engine? Well, this concept is very abstract, the best chess players play, analyze and observe millions of games which gives them that game sense. As for today, this intuition has not yet been programmed fully. (Podlesak, 2019.) There have been different approaches to this, the main ones being Stockfish, which is the strongest traditional chess engine to date, having an ELO rating of ~3550, to put this in perspective the best chess player in the world Magnus Carlsen has an ELO of ~2862 and his best is 2882. (FIDE Ratings.) On the other hand, there has been a new approach to chess engines, which attempts to duplicate human learning experience in order to make it work better. This is accomplished by using machine learning instead of solid coding that makes it as beautiful as a stockfish. With machine learning, the computer only acquires a game of chess and basic rules and learns by playing games against it. This has been a success and the most popular engine using this method is AphaZero developed by DeepMind a Google-owned enterprise focused Lastly, the stockfish team in 2020 joined the traditional chess engine with neural networks. They created what is today as Stockfish NNUE (Efficiently Updatable Neural Networks). On September 02, 2020, Stockfish 12 was released with a huge jump in playing strength due to the introduction of this technology.
The architecture of Stockfish NNUE is extremely different from the neural networks AlphaZero uses.
AlphaZero uses an extremely deep, convolutional neural network with as many as 40 layers.

Evaluation function
Testing work is a very important module, today the main improvement of chess engines comes in transforming the test function. The input of this module is a chess area, and the output is a number. If this number gives us a test of 0, it means that the area is equal to both players. The higher the positive number, the greater the gain in white, the lower the negative number, the greater the benefit in black. There is a different data algorithm that looks like it will come out with a test. This has been hardcoded to mimic a grand mastermind. This is fundamental for understanding chess and chess engines. Stockfish uses the following methods to calculate the evaluation: (CPW-Evaluation.) • Material A value is assigned to each piece in chess, for example, a knight is worth 3 points and a queen 9. And the material will be the sum of all available pieces. Material value is what influences most of the evaluation. Each piece receives a bonus value depending on what square the piece is in. Each piece of each color has its own table. The value of a square for a certain piece can vary through the game and with this we can achieve different goals, like pawns advancing in the endgame or knights and bishop developing in the opening stages of the game. To make a semidecent chess engine just with material and Piece-Square Tables are enough, but nowadays we will need much more to compete with toplevel engines. (Piece-Square Tables - This term is used to describe the position of the pawns on the board. This will ignore all the other pieces. To determine a good or bad pawn structure we can look if we have doubled pawns, which means, having 2 pawns on the same file or having a pawn with a clear path for promotion. This last situation is known as a passed pawn. There are more things to take into consideration for pawn structure like isolated pawns, pawn islands, etc. This will be all hardcoded considering the benefits and inconveniences of each situation. (Pawn Structure -Chessprogramming Wiki.) • Evaluation of Pieces Every piece can have its evaluation changed depending on the board state, for example, if the rook is on an open file the value of the rook will go up significantly. On the other hand, if your black square bishop is trapped and cannot move because it is behind black squared pawns, the value of that bishop will go down.
Each of the 6 different pieces will have some specific rule to either make its value better or worse. Controlling the center squares in chess is a really good strategy that generates space and allows pieces to get to the desired spaces. Normally the center is controlled by pawns, but we have seen some modern chess openings where the center is controlled by pieces from far away. (Chess Programming Part VI.)

• Connectivity
It is important that every piece is well defended. So having defended pieces will make the evaluation go in your favour while having undefended pieces will work against the evaluation function. (Levinson & Weber, 2001.) • King Safety For humans, king's safety is the number one priority in chess. This task is very hard to put into code. There are a lot of factors that can go inside the calculation of king's safety, for example, how well structured is the pawn shield in front of the king, how many attacking pieces does the enemy have, are there files open near the king. (Tesauro, 2001.) • Tempo Tempo refers to the ability to make moves. The fewer moves you need to get a piece to a certain square you will gain a tempo. This is important in chess because if you always make a threat on every move, your opponent loses a tempo responding to it, but the moment you make a passive move, your opponent can take the initiative and gain tempo.
These are the variables that go into a chess evaluation function. It is to be noted that every single detail has been human conclusions and hardcoded. These concepts are a part of the experience of humans after centuries of chess games being played. Nevertheless, every time we discover new positions or different tactical approaches to different positions, this is what makes conventional chess engines flawed. It is impossible to make a truly objective evaluation function, this is the reason why the evaluation function keeps changing every year to keep up with the new chess discoveries and cover up these little details.

Search function
This module will be the one in charge to calculate the different variants given a position. This is a very complex thing to do, and once again it tries to mimic humans. You may be able to make a move that improves your position but leads to forced mate in several moves from the opponent. Calculating the variables optimally and knowing which will be the best path to pursue is critical for winning at chess. There have been different algorithms that chess engines have used over the years.
We can differentiate them into two types: brute-force search and selective search. In the earlier days, the selective search was favoured. This type had a major risk since it had the possibility to oversee some tactics. Nowadays, with the amount of computing power available, programs are closer to a brute-force search, but they still use some characteristics of the selective search. The most popular search algorithms used today are minimax and alpha-beta.
• Minimax John von Neumann, in 1928 classified chess as a two-player zero-sum game with perfect information. And, as he stated, there will be an optimal solution to it. First, to understand this algorithm the concept of a zerosum game needs to be clear. A zero-sum game is when one person's gain is equivalent to another's loss, so the net change in benefit is zero, for example, if in a game of chess player A has an advantage of 1 queen is because player B has an advantage of -1 queen. One player can only win what another player has lost. The previous example is a very simplified version of chess, but you can get a similar conclusion after analyzing a chess position. Now that the concept of a zero-sum game is understood, the minimax algorithm works with the idea that both players will go for the best possible move. This is achieved by doing the play that suits the opponent the less. So basically, you are minimizing the maximum loss. (Beal, 1982.) Figure 11. Example of Minimax Algorithm with Tic-Tac-Toe(Game Tree for Tic-Tac-Toe Game Using MiniMax Algorithm.) In the previous figure, we can see a minimax algorithm in action, it is working with a depth of 3. All of X's moves will be calculated and then all of O's possible answers till we reach 3 moves for the starting position. Player X will choose the highest number and Player O will choose the lowest number. So, even if you choose the left or the middle path, you can still win as player X, if player O plays perfectly you will not win. In this example, we are just dealing with values of -1 or 1 in chess. The evaluation function will give you a higher range of numbers, but the same concept still applies. One player will be denominated max and he will choose the highest value and the other player will be min, and he will choose the smaller value. In this new example, we have a depth of 4. On move 3, min chooses the lowest value from move 4. Min chooses 10 since 10 is smaller than infinite, it also chooses -7 since -7 is lower than -5. On the other hand, on move 2 the player max chooses the highest possible number from the 3rd row and this continues till we reach row 0. One important aspect that can be observable is the number of different states and positions are derived from the first, the growth is exponential. In the middle game of a chess game on average, you have 30 possible plays. This would mean that having a depth of 5 will lead to 30^5=24.300.000 positions. Even by today's standards of technology, it will be impossible to approach high depths.
• Alpha-beta pruning Alpha-beta algorithm is an improvement of the minimax search algorithm that reduces the number on a large scale the number of nodes evaluated. Stockfish 12 is using, with additional improvements this algorithm. To illustrate this with a real-life example, suppose somebody is playing chess, and it is their turn. Move "A" will improve the player's position. The player continues to look for moves to make sure a better one hasn't been missed. Move "B" is also a good move, but the player then realizes that it will allow the opponent to force checkmate in two moves. Thus, other outcomes from playing move B no longer need to be considered since the opponent can force a win. The maximum score that the opponent could force after move "B" is negative infinity: a loss for the player. This is less than the minimum position that was previously found; move "A" does not result in a forced loss in two moves (Alpha-Beta Pruning, 2021). To further explain how it works we will use as an example the next tree: Figure 13. Alpha-beta pruning example (Alpha-Beta Pruning, 2021) In this example, we have the max player playing with the white pieces. It has calculated the next possible results with a depth of 4. First, starting at the bottom min chooses between 5 and 6 and it chooses 5. Then min has to choose again between 7, 4, or 5, this time it stops evaluating positions at value 4 because the next move will be max turn to play and it will have to choose between 5 or 4, and since max is always going to pick 5 (the highest number possible), there is no need to keep exploring that branch since max will not pick it so we can discard it, or in the case of this algorithm, function alphabeta(node, depth, α, β, maximizingPlayer) is if depth = 0 or node is a terminal node then return the heuristic value of node if maximizingPlayer then value := −∞ for each child of node do value := max(value, alphabeta(child, depth − 1, α, β, FALSE)) α := max (α, value) if α ≥ β then break (* β cutoff *) return value else value := +∞ for each child of node do value := min(value, alphabeta(child, depth − 1, α, β, TRUE)) β := min(β, value) if β ≤ α then break (* α cutoff *) return value (* Initial call *) alphabeta (origin, depth, −∞, +∞, TRUE) prune it. On the second main branch. We start from the bottom again. Min chooses 6 and then it has the option to choose between 6 or 9, and pruning happens again. Since we have the same value in both children nodes and max will choose the highest it serves no purpose to continue exploring that branch, so we prune it.
On the third main branch, we will start at depth 2 where min has to choose between 5 or 8. When the value of 5 gets evaluated we immediately prune the rest of the tree since we now max will choose 6 or higher, there is no point to keep exploring the rest of the tree to get a number lower than 5.
To translate this concept to programming we have two limits α and β, that will correspond to the most convenient evaluation at the moment. The initial value will be -∞ and +∞ and will be updating when different variants will be evaluated. The pseudo-code a minimax alpha-beta pruning is as follows: Figure 14. Pseudo-code of minimax with alpha-beta pruning (Alpha-Beta Pruning, 2021) As previously mentioned, this algorithm is very similar to minimax but with a well-written program, a standard minimax tree with x nodes can be reduced close to the square root of x nodes. This is heavily reliant on how well-ordered the tree is. If the best move is always explored first, you eliminate the most nodes, but always knowing what move will be the best in a given position is a complex task. Therefore, good move ordering is extremely important, and it is where a lot of the effort in writing a successful chess engine resides. To get this result the typical move ordering will be: One of the problems that may arise is the Horizon effect. This effect is caused by the depth limitation of the search algorithm. This happens when a negative event is inevitable but postponable. The engine will only be able to analyze a partial part of the search tree, it will choose a move that will seem to avoid the threat, but this is not the case. Figure 15. Example of the horizon effect function quiescence_search(node, depth) is if node appears quiet or node is a terminal node or depth = 0 then return estimated value of node else (recursively search node children with quiescence_search) return estimated value of children function normal_search(node, depth) is if node is a terminal node then return estimated value of node else if depth = 0 then if node appears quiet then return estimated value of node else return estimated value from quiescence_search(node, reasonable_depth_value) else (recursively search node children with normal_search) return estimated value of children In the situation presented in figure 15, the black bishop is trapped. No matter what the black does white can always manoeuvrer the rook to a1 in two moves and capture the bishop in the third move. If we have a chess engine with a depth of 6, what could happen is that the best move for black suggested by the engine will be to push pawn e3, just to force the king to capture the pawn, then pushing the remaining pawns to force the king to keep capturing the pawns in an attempt to save the bishop, but that only will delay the capture of the bishop since it's trapped and will lose you 3 passed pawns. Probably the best line of play for black will be to exchange the bishop for a pawn and try to hold the game with connected pass pawns against a rook.

Chess databases
Another important factor to consider is the use of game databases. There are millions of games stored in the different chess databases. During the first moves of the game these databases are very useful, we can look at how many times victory was achieved with certain openings. The best engines in the world still use opening books from the best chess Grandmasters. This eliminates the need for the engine to calculate the best lines during approximately the first ten moves of the game, where the positions are extremely openended and therefore computationally expensive to evaluate. As a result, it places the computer in a stronger position using considerably fewer resources than if it had to calculate the moves itself. (Opening Book -Chessprogramming Wiki.) On chess engines, the endgame is approached by Nalimov Tablebases. This is a database that stores all the positions with a small piece count. These positions are already determined as winning, losing, or drawing for the player that moves. Nowadays it is known the outcome of a chess position with at most 7 pieces on board since 2012. This function will prioritize the nodes that form the moment, are considered the best for having led to better results. This selection process will continue recursively for all the nodes in the tree until a node that has not been expanded upon is reached. After this process, the node child nodes are calculated the engine will pick a random child node since the upper bound function will return a random result for every child node. After randomly selecting the child there will be a random simulation of the game where that move was played. This is why the algorithm gets the Monte Carlo name. Even though the game is not truly random, there have been different heuristics that will take more time for the computer to process but will lead to better results. Finally, the algorithm enters the backpropagation where it updates the value of the previous nodes by

Benefits of using Neural Networks
Using neural networks will bring different advantages over conventional chess engines is the use of Graphical Processing Units (GPUs). With the use of neural networks, AlphaZero can take advantage of the parallel computing power that the GPUs provide. On the other hand, engines like Stockfish were stuck with only using CPU power which is slower since the operations that the engine used were good on a sequence that is where CPUs excel. AlphaZero uses specialized GPUs called TPUs that are google designed hardware to optimize the operation of neural networks. That means adding an additional dimension to the work being done. While GPUs are designed to essentially do huge amounts of parallel arithmetic and trigonometry, TPUs are optimized to rapidly do huge amounts of Matrix multiplication, the fundamental mathematical abstraction of what neurons do. Nowadays, with the latest advancements of Stockfish, they are using a hybrid between conventional chess engines and neural networks so it can benefit from both. (Chess.com) One of the other benefits of neural networks is the adaptability that it has. The engine only knows the rules of chess and does not have hardcoded rules that humans have made up after years of analysis and experience, so, it becomes easy when you want the engine to learn different variants of chess. This can be useful to find new ways to make chess engaging and learn the best way of playing. There was a paper published by DeepMind about different chess variants, and how the different rules that they added to chess changed the value of certain pieces or how favored was white to win over black. This would be a harder task to accomplish with conventional chess engines since change a few rules of chess can dramatically change the optimal way of playing and all the years of analysis

Introduction
A chess engine is going to be developed with the goal of it having a low-medium level of chess. Like we have seen before there are several approaches on how to tackle this problem. The purpose of this project is not to build the best chess engine, it is to build a lowmedium level engine that people can enjoy and observe the changes of its behavior when new functionalities to the search and evaluation functions are added. For achieving this goal, we must take into account the speed of the engine and the computing power needed for it to work properly. Therefore, I have chosen to develop this chess engine using a traditional approach since using the neural network approach will need more computing power and will take longer to process the different moves.

Programming
For developing this chess engine, I used Python. This programming language is not the fastest one, but it has a lot of documentation and modules that made the development of this project much easier and smooth.
Within python the python-chess library was used, this library already has the movement generator, board, and interface already implemented. For starters, I developed the scholar's mate using the python chess library to learn the basics. Figure 20. Implementation of scholar's mate using python-chess. This is a basic implementation of scholar's mate. For the board to be displayed on anything else than ASCII I made use of Jupyter Notebook, this will help with the visualization of the board. With the python chess library, it is possible to make an SVG board that Jupyter notebook can display successfully to enhance the visualization of this project.
For starters, I programmed a player that made random moves on the chessboard, to test if the first iteration of the chess engine would beat it: Figure 21. Random player implementation.
After testing this random player was successfully working a function that made possible games between computers had to be implemented and the code in figure 21 was implemented. This code makes will show the final board and the number of moves that the chess game took. It will display a message saying the result of the game and the number of moves it took, also it will display in SVG the final board and a PGN file with the moves played in the given game.
This can be something that can be explored in the future development of the project.
After all of this, the first iteration of the chess engine was created. It was a simple engine that only counted material on the board. It did not take into account any other things, just what pieces were on the board and it has a value assigned to the different pieces. The values assigned in these engines are the ones Tomasz  This iteration of the engine is still bad since it will not look further than just 1 move and will capture pieces aimlessly, without considering if the piece we just capture leaves creates any weaknesses in the position.
Nevertheless, this engine still plays better than the random player implemented earlier.
The games often end in draws since the engine has no knowledge of endgames and when the enemy king is the only piece left, it just makes random moves since it does not have a piece to capture, often leading to draw by repetition or stalemates.
For these reasons, the next step of this engine is to integrate a minimax search so it can look deeper, spot potential traps, and see when you have mate in a given depth. This implementation came with great improvements in play but, doing the minimax with a depth of 3 was consuming too much time per move, I decided to add a timer that tells me how much time it takes for the minimax algorithm to come up with a move and, with a depth of 3, in the starting positions only took around 5-6 second to find a move. But, in the midgame where there are more pieces developed and more possibilities of moves it can take more than 1 minute per move. For pawns, the engine is rewarded by pushing them forward and it is discouraged to leave the central pawns unmoved. Knights get better scores when they move towards the center, bishops are incentivized to stay in long diagonals while rooks prefer to infiltrate to the seventh rank while avoiding the a and h files. Queen also prefers to move to the center than stay on corners, since it will help it cover more squares. Right now these square-piece tables are static, they do not vary with the state of the game, so they are pretty general. Figure 26. Implementation of the piece-square tables in the evaluation function.
The engine still struggles in the early game and tends to make the same moves every match since it cannot be executed at high depths and always follows the same piece-square tables. Therefore, the next step to improve this engine is to add an opening book that it can use to help with the early stages of the game. This opening book will be implemented using the Polyglot format because the python-chess has a function that can make use of this format without trouble. The opening book of choice will be one approved by Stockfish. For now, the engine will look for positions inside the book and if the position is found, the engine will make a random weighted choice between all the different moves. If the position reached is not in the book, the engine will continue to perform as it did before with the old evaluation function and alpha-beta search tree.
I will be sharing some code and the key ideas and concepts that are involved, the complete code can be found here. Convolutional layer with a flatten layer proceeding it. It contains a 'same' padding parameter as we do not want to change the overall dimensions of the input during the flow.

Result
What I have learned from these results is that the AI knows how to play chess, but it doesn't really play at a good level. Stockfish was very easily able to beat it, this was expected as the AI was trying to imitate stockfish and we can't expect it to be better than the data it was trained on. My over the board rating is around 1300 elo and I was comfortably able to beat it. My guesstimate is that this AI might be around 600 elo or even lower like a beginner in the game. It starts off well, but makes blunders quite often and ends up losing the games.

Analysis of games played.
Now I will show and analyze Some test games between different engine strengths. With these different tests, we can see if the engine is responding as expected to the new improvements. Every game will include the player that played with each color, the result of the game, the moves that were played in PGN format so everyone can check how the game developed, for example, in websites like lichess.org, and a visual representation of the board when the game ended. This game displays interesting behavior. We can see that the engine if it does not see an improvement in the next 4 moves, it will just pick the last move from the movement generator list that does not make the position worse, from moves 4-8 since black did not have any pieces developed and the algorithm cannot see really deep it only moved the rook aimlessly from h1 to g1, until black left the knight in b4 undefended.
The engine saw it and attacked the knight on the next move. Also, with the white knight on g5, it could capture the bishop on e6 for a long time, but the white waited till black blundered and left the bishop unprotected to capture it. The engine was also able to win the game unlike the previous iteration, the mate was found by having a very good position and not by creating mating patterns.