How I created an open-source implementation of Pokémon Chess
18/01/2024
After trying Pokémon Chess, I wanted to create an AI that could determine the best possible type configuration. To do this, the first step is obviously to create a suitable environment.
But what exactly is Pokémon Chess?
Pokémon Chess is a board game idea by YouTuber Little Z, which aims to combine the classical chess with some mechanics of the Pokémon franchise. A web version has been active for the last 6 months, and a tournament between popular Pokémon YouTubers (or Poketubers) was held as an inauguration event. A full video of the competition was uploaded in the creator's channel as a summary, which also explains the rules as they take place in the different games.
Pokémon Chess' rules can be found in the Discord server created for the community, where users can also play ranked games against each other. There is even an official leaderboard that contains the best players. It is recommended that the reader gets familiar with the rules before reading this post.
The objective of this article is to show how to implement a full version of Pokémon Chess using Python's library Pygame.
Since Pokémon Chess is heavily based on classic chess, I decided to use one of the many open-source Pygame implementations of chess. Particularly, I decided to use Michael Maranan's (x4nth055 on Github) implementation, which can be found in this repository. As a beginner to Pygame, his solution came with an explanatory guide that described his code.
The implementation consisted on a series of classes (Square, Board, Piece and the specific piece classes, which inherited from the generic Piece class) and a main.py file. It also included images for the different types of pieces, that were always displayed, and highlighted the selected piece and the possible squares it could move to. I tried to run the main.py file and became impressed by the emergent window that showed a full chess board and let me play the classic two player game.
I did not like the art style for this pieces, so I searched for other set of images to replace them. After a quick Google search, I came across this one, much more similar to the ones used by the most popular chess websites. After a quick replacement and a minor code modification, the board now looked like this:
Once I had this starting point, it was time to start making changes. The code, although not the most efficient, was very easy to read and modify. I quickly became familiar with the logic and decided to begin the journey by implementing the type effectiveness. I chose to first modify the GUI so that each piece displayed its associated type, which would be configured at the start of the game through two csv files that would include the white and black piece types.
Adding types to normal chess
If I wanted to display the usual types, I needed a sprite for each of them. I decided to use this image as a basis, which was free to use for personal projects. Some cropping work and background removing (tip: change the background color so that it is easily detected) after, the images were ready to be used. I now just had to learn how to display them. I altered the pieces' builder functions so that they now received a type argument. In the general Piece class, I also loaded the corresponding type image alongside the piece image. The resulting builders for the Piece and Rook classes is shown below. The -50 in the second transformation is used instead of -20 because the type images are not squared, and the ratio between their dimensions is close to 5:2. Using -50 guarantees the image is not deformed when displayed.
To display the pieces' types, it is enough to update the draw method of the Square class so that it now displays the type image at the midpoint between the midleft and bottomleft locations. I will show the final code later as there are more modifications to be done. The result of this is shown below for a sample initial configuration.
This looks very nice! Now we need to work on the logic of the program so that captures take into account type effectiveness. To sum up, a super-effective capture lets the piece move again, a not very effective capture also removes the attacking piece, and a capture with no effect simply passes the turn to the other player. The most problematic situation of these is the first one, since basic chess does not consider any case in which a player can act two times in a row. To solve this, the first version I implemented simply flipped again the turn if a capture had been super-effective, as shown below.
However, this does not follow exactly the Pokémon Chess rules. If a capture is super-effective, the player is allowed to make a second move, but that move can only be done with the same piece that captured the first piece. To solve this, I had to modify the Board class so that it now had a variable to know when a super-effective capture has taken place, called chain_move. The updated code for the handle_click method of the Board class is shown below. The code for the move method of the Piece class will be shown later, as more modifications are necessary.
Notice the new version does not allow the player to change the selected piece. Another minor adjustment we need to take into account is that the player can choose not to make the additional move, so an option for this needs to be implemented. The easiest way to solve this is to add to the possible moves of the piece the possibility of clicking the piece again so that it stays on the same square (obviously, this needs to only be available after a super-effective capture, not always).
Once this first part is done, I discovered some limitations of the chess implementation I had used as a base. I will briefly comment on how I solved each of them:
· Implementing en-passant moves: I used a variable to abilitate en-passant moves when a pawn had just moved 2 vertical squares, and only for the following move. Some caution was needed when considering the possibility of performing en-passant after a super-effective capture, see the example below.
· Adding promotions to other types of pieces besides the queen: If a pawn reaches the last (or first) rank, four images will be displayed instead of it, and the player's turn will not end until the promotion square is clicked. The location of the click determines which piece the pawn is promoted to. An example can be seen below (notice the new piece is allowed to move again because it was a super-effective capture). Only exception to this is if the promotion move is a not very effective capture, in which case the turn ends inmediately and no piece is chosen.
· Removing the existance of checks and checkmates: Checks and checkmates do not exist in Pokémon Chess, so games are decided when at least one of the two kings is captured, and there are no forced moves to escape checks (you can also run into a check or suicide your king by doing a not very effective capture). Removing the checks condition is easy enough, but certain situations in which the king is captured with a super-effective move (the game must end, but this was only checked after each turn ends) or when both kings are removed at the same time (one king captures the other with a not very effective capture, so the game is a draw) proved to be more troublesome.
· Implementing draws by threefold repetition or by the fifty-move rule: The latter was easy to implement by maintaining a counter of the remaining moves until a draw, which was reset after a capture of after a pawn move and decreased by one at the end of each turn. For the repetition, each configuration the game went through after the end of each turn had to be saved, including the castling rights and the associated types of each piece (contrary to regular chess, two knights of the same colour are not considered equal). The configurations would be stored as strings in a hashmap that contains the number of times that configuration has occurred during the game. If any configuration reaches three occurrences (including the initial one), the game is declared a draw.
After having a full deterministic version of pokemon chess, it is time to implement the most hated mechanic in the game, the critical hits and misses that characterise the luck aspect of the game. Every capture in Pokémon Chess has a 1/10 chance to miss and a 1/16 chance to critical hit. Essentially, misses behave the same way as if a move had no effect, while critical hits are like super-effective hits. Also, if you are wondering, these cannot happen at the same time, and if a capture is not very effective, a critical hit will still behave the same way as a super-effective move (not with no effect moves though).
This was not too hard to implement, but some aspects need to be taken into account. For instance, a King which has never moved and whose first move is a capture that misses should preserve its right to castle. The full code for the move method of the Piece class, which also includes how captures are handled, is shown below:
Finally, I took the time and liberty to implement some notation for Pokémon Chess moves that would be able to show critical hits, super-effective moves and so on. Once a game is finished, the PNG of the game would be shown on console, following the rules available here.
This finishes the explanation of the implementation of the Pokémon Chess game. I will use this environment to train an AI that (hopefully) will outperform humans. To play with this yourself, you can access a deterministic version (without critical hits or misses) through this link (this does not show any console). This version of the game can also be played setting the DETERMINISTIC variable to False in the main class.