Checkers is a classic two-player board game that has been enjoyed by people of all ages for generations. It is a strategy game that requires players to move their pieces across the board, capturing their opponent's pieces and ultimately trying to reach the other end of the board with one of their pieces to become a king.
In this article, we will be making a Checkers game in Python. Our goal is to provide an overview of the game's codebase, breaking it down into several classes that work together to provide the game's functionality. We will cover the installation and setup process, the main class, the game class, the board class, the tile class, the piece class, the pawn class, and the king class.
Before we can begin working on our game, we need to make sure we have all the necessary tools installed on our computer. The first thing we need to do is to make sure we have Python installed. You can download Python from the official website.
Also, we need to install the Pygame library. Pygame is a set of Python modules that allow us to create games and multimedia applications. To install Pygame, open up the command prompt/terminal and type the following command:
Finally, we need to create a folder named "images" inside the Checkers directory. This folder will contain the images for our game pieces. Inside the folder, place the following image files: black-pawn.png, black-king.png, red-pawn.png, and red-king.png. You can access the pictures here.
The Checkers class has an __init__() method that takes in a screen parameter which initializes several class attributes, including screen, running, and FPS. screen represents the game window, running is a boolean that determines whether the game is still running, and FPS is a Pygame clock that limits the game to a certain frame rate.
The _draw() method is a helper method that takes in a board parameter and draws the game board onto the screen using the board.draw() method. It then updates the display using pygame.display.update().
The main() method is the main game loop. It takes in window_width and window_height parameters, which are the dimensions of the game window. board_size is set to 8, which represents the size of the game board. tile_width and tile_height are calculated based on the size of the window and the size of the board:
The while loop runs as long as self.running is True. Within the loop, we call game.check_jump(board) to check for any available jumps on the board. We then loop through the events in the Pygame event queue using pygame.event.get(). If the event type is pygame.QUIT (when the exit button was clicked), we set self.running to False to exit the game.
If the game is not over, we check if the user has clicked on a tile on the board using board.handle_click(). If the game is over, we display a message using game.message() and exit the game loop by setting self.running to False.
We set the window_size to (640, 640), create a Pygame display screen with this size using pygame.display.set_mode(window_size), and set the window caption to "Checkers" using pygame.display.set_caption("Checkers"). Then, we create an instance of the Checkers class called checkers, passing in the screen as a parameter. Then, we call the main() method on checkers, passing in the window_size dimensions. This code sets up the Pygame display screen, creates an instance of the Checkers class, and starts the game loop by calling the main() method.
The check_piece() method iterates over each tile on the board and checks if it contains an occupying piece. If it does, it adds to the red_piece count if the piece color is "red", or to the black_piece count if the piece color is "black". It then returns a tuple containing the red_piece and black_piece counts.
The is_game_over() method calls the check_piece() method to get the current piece count for each color. If one color has no pieces left, the other color is declared the winner, and the method returns True. Otherwise, the method returns False.
The check_jump() method checks if there is a piece that can make a jump. If there is, it sets the is_jump attribute of the board to True, sets the selected_piece to the first piece that can make a jump, and returns True. If there isn't, it sets the is_jump attribute of the board to False and returns False. Since we're creating the traditional Checkers where jump moves can't be skipped, the check_jump() will force the users to jump.
The Board class defines the behavior of a checkers game board and how it responds to user input. It has an __init__() method that initializes the board with the specified tile_width, tile_height, and board_size. It also initializes various variables such as selected_piece, turn, and is_jump:
The self.config contains the starting setup of the board for our game. The tile_list calls the _generate_tiles() method that creates each tile for the board, and the _setup() method sets the starting position of pawns in the game base on config.
The _setup() method sets up the initial board configuration by iterating through the self.config list, which represents the starting positions of the pieces, and setting the occupying_piece attribute of the corresponding tile object to a Pawn object.
First, the method extracts the x and y coordinates from the pos argument. If the coordinates are outside the board size, the method calculates which tile was clicked based on the position of the click relative to the size of each tile. Next, the method retrieves the Tile object that was clicked by calling the get_tile_from_pos() method, passing in the (x,y) coordinates of the clicked tile.
If there is no selected_piece currently, the method checks if the clicked tile has a Pawn object on it and whether that Pawn object belongs to the current player's turn. If there is a Pawn object on the clicked tile and it belongs to the current player's turn, the selected_piece attribute of the Board object is set to that Pawn object.
If there is a selected_piece already, the method attempts to move the Pawn object to the clicked tile by calling the _move() method of the Pawn object, passing in the Tile object as an argument. If the move is successful, the turn attribute of the Board object is updated to reflect the next player's turn.
If the move is a jump, the is_jump attribute of the Board object is set to True. The method then checks if there are any more valid jumps available for the same Pawn object, by calling the valid_jumps() method of the Pawn object. If there are no more valid jumps, the turn attribute of the Board object is updated to reflect the next player's turn.
The draw() method is used to draw the board and the pieces. It is called to update the display with any changes made by the handle_click() method. If a piece is selected, the tile it is on and its valid moves or jumps are highlighted.
The color property is determined by whether the sum of the tile's x and y coordinates are even or odd. If it is even, the tile is a light color, otherwise, it is dark. The draw_color and highlight_color properties are tuples representing RGB values for the tile's fill color and highlight color, respectively.
The draw() method draws the tile on the screen using pygame.draw.rect, using the draw_color property as the fill color. If the highlight property is set to True, the tile is drawn with the highlight_color instead. If there is an occupying_piece, the piece's image is blitted onto the center of the tile using pygame.Surface.blit(). The image is first centered using the get_rect() and center properties of the piece's image.
The Piece class is a parent class for all checker pieces in the game. It contains common attributes and methods that are shared among all pieces such as the position of the piece on the board, its color, and the ability to move to certain tiles on the board based on the game rules.
For the Pawn and King classes specifically, they override some of the methods of the Piece class to account for the specific rules that apply to these pieces in the game of checkers. For example, the valid_moves() method of the Pawn class returns the valid moves that a pawn can make on the board based on the game rules for a pawn (can move or jump forward/against their starting side only). Similarly, the valid_moves() method of the King class returns the valid moves that a king can make on the board based on the game rules for a king (can move or jump forward or backward).
In addition, the Pawn class includes a specific implementation for pawn promotion, which occurs when a pawn reaches the opposite end of the board. The King class does not require a specific implementation for promotion since kings are already the highest-ranking pieces in the game.
The _move() method takes in a tile object representing the new position for the piece. It first checks if the new position is a valid move for the piece by calling the valid_moves() method. If the move is valid and no jump is occurring, the method updates the position of the piece and the occupying_piece attribute of the relevant tiles to reflect the new position of the piece. If the piece is a pawn and reaches the last row, it will be promoted to a king.
If a jump is occurring, the _move() method checks if the new position is a valid jump move by calling the valid_jumps() method. If the move is valid, the method updates the position of the piece, the occupying_piece attribute of the relevant tiles, and removes the jumped_piece (opponent's piece that caused the jump). The method checks if the pawn has reached the last row and promotes it to a king if necessary.
The Pawn class is a subclass of the Piece class, pawns are the pieces used from the start of the game. The class has an __init__() method that calls the parent's __init__() method to initialize the object's x and y position, color, and board. It also loads an image for the pawn and assigns it a notation of 'p':
The valid_moves() method checks all possible moves for a pawn on the board and returns a list of valid moves. It does so by iterating through the possible moves and checking if each move results in a valid tile position on the board that does not contain any occupying piece. If the tile position is valid and empty, it appends the tile object to the list of valid moves.