Turn your code into any language with our Code Converter. It's the ultimate tool for multi-language programming. Start converting now!
The chess game is a pretty cool project idea for intermediate Python programmers. It's good practice for making apps with GUIs while getting good at using classes. In this tutorial, you learn about:
pygame
.Related: How to Make a Hangman Game in Python.
Before we start coding, let's first install the pygame
module in the terminal:
$ pip install pygame
Once we installed the pygame
, let's move into setting up our environment by making the py files and folder we're using in this order:
> python-chess
> data
> classes
> pieces
/* Bishop.py
/* King.py
/* Knight.py
/* Pawn.py
/* Queen.py
/* Rook.py
/* Board.py
/* Piece.py
/* Square.py
> imgs
/* main.py
Move the images of the chess icons you'll use in the python/data/imgs/
directory. Make sure your image files are named [color's 1st letter]_[piece name].png
just like this:
If you don't have chess icons, you can use mine here.
And now we're done setting up; we can start coding now. Our chess game has two main code parts; creating the board and creating the pieces. The board will mainly focus on square positions and game rules, while the pieces focus on the piece they represent and the moves it has.
Making the Board
Let's start by making the Square
class. The Square
class creates, colors, position, and draw each chess tile inside our game window:
# /* Square.py
import pygame
# Tile creator
class Square:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.abs_x = x * width
self.abs_y = y * height
self.abs_pos = (self.abs_x, self.abs_y)
self.pos = (x, y)
self.color = 'light' if (x + y) % 2 == 0 else 'dark'
self.draw_color = (220, 208, 194) if self.color == 'light' else (53, 53, 53)
self.highlight_color = (100, 249, 83) if self.color == 'light' else (0, 228, 10)
self.occupying_piece = None
self.coord = self.get_coord()
self.highlight = False
self.rect = pygame.Rect(
self.abs_x,
self.abs_y,
self.width,
self.height
)
# get the formal notation of the tile
def get_coord(self):
columns = 'abcdefgh'
return columns[self.x] + str(self.y + 1)
def draw(self, display):
# configures if tile should be light or dark or highlighted tile
if self.highlight:
pygame.draw.rect(display, self.highlight_color, self.rect)
else:
pygame.draw.rect(display, self.draw_color, self.rect)
# adds the chess piece icons
if self.occupying_piece != None:
centering_rect = self.occupying_piece.img.get_rect()
centering_rect.center = self.rect.center
display.blit(self.occupying_piece.img, centering_rect.topleft)
The first thing we're gonna do is to make a class for making chess Square
. Let's start by adding the __init__()
function to get the square's width
, height
, x
for a row, and y
for the column.
With this basic information, we can fulfill other variables using them. As you see above, we have self.x
and self.y
while we also have self.abs_x
and self.abs_y
. self.abs_x
and self.abs_y
dictates where the chess tile is assigned to be drawn inside the window, and we compile them both in self.abs_pos
.
The self.color
tells the square tile should be light colored if it is divisible by 2 or instead dark if not, while the self.draw_color
tells the color configuration for light and dark. We also have self.highlight_color
which we use to highlight the tiles with the possible movement of a piece if it was selected. The self.rect
configures the width, height, and location (using self.abs_x
and self.abs_y
) of a square or tile.
The get_coord()
returns the name of the tile depending on its x
and y
based on the real board. Letters symbolize rows, and the number symbolizes columns. Like "a1", it is the bottom leftmost tile in a chess board.
The draw()
, executes the configurations we did by drawing the tile in the canvas, in the color it was assigned. The second if
statement tells that if the square has a piece in this position, you should access its icon and place it inside the tile.
Now we have a class for making a square. Let's make another class for handling tiles and the whole board.
# /* Board.py
import pygame
from data.classes.Square import Square
from data.classes.pieces.Rook import Rook
from data.classes.pieces.Bishop import Bishop
from data.classes.pieces.Knight import Knight
from data.classes.pieces.Queen import Queen
from data.classes.pieces.King import King
from data.classes.pieces.Pawn import Pawn
# Game state checker
class Board:
def __init__(self, width, height):
self.width = width
self.height = height
self.tile_width = width // 8
self.tile_height = height // 8
self.selected_piece = None
self.turn = 'white'
self.config = [
['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'],
['','','','','','','',''],
['','','','','','','',''],
['','','','','','','',''],
['','','','','','','',''],
['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'],
['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
]
self.squares = self.generate_squares()
self.setup_board()
def generate_squares(self):
output = []
for y in range(8):
for x in range(8):
output.append(
Square(x, y, self.tile_width, self.tile_height)
)
return output
def get_square_from_pos(self, pos):
for square in self.squares:
if (square.x, square.y) == (pos[0], pos[1]):
return square
def get_piece_from_pos(self, pos):
return self.get_square_from_pos(pos).occupying_piece
In making the whole chess board, it's important to know first what is the width
and height
of the game window so we can divide it into 8 rows with 8 columns to identify our tiles' exact size.
The self.config
represents the chessboard configuration with a 2D list having our pieces with their default position. Below it, we configure the self.squares
with a value calling our self.generate_squares()
for making chess tiles and putting them all in a list.
Now let's create the other parts of the Board
, including the self.setup_board()
we called above.
def setup_board(self):
for y, row in enumerate(self.config):
for x, piece in enumerate(row):
if piece != '':
square = self.get_square_from_pos((x, y))
# looking inside contents, what piece does it have
if piece[1] == 'R':
square.occupying_piece = Rook(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
# as you notice above, we put `self` as argument, or means our class Board
elif piece[1] == 'N':
square.occupying_piece = Knight(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'B':
square.occupying_piece = Bishop(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'Q':
square.occupying_piece = Queen(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'K':
square.occupying_piece = King(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'P':
square.occupying_piece = Pawn(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
The setup_board()
creates each piece, and puts them in their respective place by mapping the self.config
with the whole board. If the current value of piece
in self.config
is an empty string or ''
, then the tile must be empty, and if not, it will access its respective tile position through the current value of x
and y
. Each piece
from self.config
will be identified according to the capital letter it has, except the Knight
.
If we got 'N'
, then it's a Knight
if 'P'
, then it's a Pawn
. If 'R'
, then it's a Rook
, 'B' for Bishop
, and so on. After we configure the letters, we'll overwrite the current square.occupying_piece
with a value of their piece class with the color depending on the first value of the piece
string. As you noticed here and in the other statement:
if piece[1] == 'R':
square.occupying_piece = Rook(
(x, y), 'white' if piece[0] == 'w' else 'black', self
We put a self
as an argument for the Rook
class. That means we put our current class, Board
, as an argument.
We need a function that detects each click in our game. So let's make handle_click()
in our Board
class:
def handle_click(self, mx, my):
x = mx // self.tile_width
y = my // self.tile_height
clicked_square = self.get_square_from_pos((x, y))
if self.selected_piece is None:
if clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece
elif self.selected_piece.move(self, clicked_square):
self.turn = 'white' if self.turn == 'black' else 'black'
elif clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece
The handle_click()
accepts the x (mx
) and y (my
) coordinates of where you click inside the game window as an argument. The x
and y
variables inside this function compute what row and column you clicked, then we pass its outcomes to clicked_square
to get the square or the tile.
This configuration can now receive our every click inside the game window. The following if/else statements process our click if we're making a move or just clicking around.
It all works once you clicked somewhere inside the game window so let's assume that you're playing the game using a white piece and you've already clicked. If we haven't selected any piece yet, it will look as if the tile you clicked has a piece, and if it's your colors turn, and if yes, it will be your self.selected_piece
.
With the help of other classes, your piece's possible move will be highlighted in the game. After selecting a piece's move, it will convert the self.turn
into the next player's piece color.
Now that we have a selected piece and chosen a move, it will make the move. I'll explain the other moving later as we make the classes for that piece.
Let's add another feature for the Board
class; we are adding functions that check if a player is in check or checkmate.
# check state checker
def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
output = False
king_pos = None
changing_piece = None
old_square = None
new_square = None
new_square_old_piece = None
if board_change is not None:
for square in self.squares:
if square.pos == board_change[0]:
changing_piece = square.occupying_piece
old_square = square
old_square.occupying_piece = None
for square in self.squares:
if square.pos == board_change[1]:
new_square = square
new_square_old_piece = new_square.occupying_piece
new_square.occupying_piece = changing_piece
pieces = [
i.occupying_piece for i in self.squares if i.occupying_piece is not None
]
if changing_piece is not None:
if changing_piece.notation == 'K':
king_pos = new_square.pos
if king_pos == None:
for piece in pieces:
if piece.notation == 'K' and piece.color == color:
king_pos = piece.pos
for piece in pieces:
if piece.color != color:
for square in piece.attacking_squares(self):
if square.pos == king_pos:
output = True
if board_change is not None:
old_square.occupying_piece = changing_piece
new_square.occupying_piece = new_square_old_piece
return output
For every move we make, the is_in_check()
function will be called, whenever the board_change
is not empty.
In the first iteration, it locates the position of the old tile, passes its current piece in changing_piece
, and empty that tile while in the second iteration, it catches the new tile position and passes its current piece to new_square_old_piece
and give it a new piece from changing_piece
.
Once our changing_piece
is not empty, it'll try to identify if it's a King
by getting its self.notation
. If so, it'll override the king_pos
and give it the value of new_square.pos
.
Note: The self.notation
is a variable from the pieces' class, that serves as an identification containing their letter symbols.
The next thing we'll try to identify is what the enemy piece does to do the check to our player's King
, where we check starting by if piece.color != color
.
for piece in pieces:
if piece.color != color:
for square in piece.attacking_squares(self):
if square.pos == king_pos:
output = True
With the following code above, we can iterate through enemy pieces and check their attacking_squares
, which gets all the possible moves of a piece. If a piece
has a position in attacking_squares
the same value as king_pos
, which means one of the players is checked, so we set the output
to True
. The output
tells if a King
is in check or not, so we have to return it.
Now let's make the is_in_checkmate()
function for identifying if we have a winner yet:
# checkmate state checker
def is_in_checkmate(self, color):
output = False
for piece in [i.occupying_piece for i in self.squares]:
if piece != None:
if piece.notation == 'K' and piece.color == color:
king = piece
if king.get_valid_moves(self) == []:
if self.is_in_check(color):
output = True
return output
Once we get the King
the same color as the argument we passed, it'll try to see if it has any moves left. If none, then it'll check if the player is in check. If that's the case, then it will return the value of the output
which is True
, which means the side of the color we passed is checkmate.
Now we have all the board configurations; it's time to add the final function for the Board
class which is the draw()
function:
def draw(self, display):
if self.selected_piece is not None:
self.get_square_from_pos(self.selected_piece.pos).highlight = True
for square in self.selected_piece.get_valid_moves(self):
square.highlight = True
for square in self.squares:
square.draw(display)
This function highlights all the possible moves of a piece once selected while it is its color's turn.
Learn also: How to Make a Tetris Game using PyGame in Python.
Making the Pieces
Now we're finished with Board
class, let's make another class for pieces in Piece.py
.
Let's start by adding a function that gets all the available moves and a checker if the next player got checked by the previous player:
# /* Piece.py
import pygame
class Piece:
def __init__(self, pos, color, board):
self.pos = pos
self.x = pos[0]
self.y = pos[1]
self.color = color
self.has_moved = False
def get_moves(self, board):
output = []
for direction in self.get_possible_moves(board):
for square in direction:
if square.occupying_piece is not None:
if square.occupying_piece.color == self.color:
break
else:
output.append(square)
break
else:
output.append(square)
return output
The get_moves()
gets all the available moves of the current player, including attacking the enemy piece. If an opponent piece is in range of a piece's move, the piece can capture it where its range will limit on these opponent's piece tile position through output.append(square)
then break
unless the piece is Knight
which can move in an 'L-shape'.
def get_valid_moves(self, board):
output = []
for square in self.get_moves(board):
if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
output.append(square)
return output
Before proceeding for our current player in making a move, the get_valid_moves()
checks first if the last player does a move that checked our current player. And if not, then it will return the available moves.
To make the pieces work, we're adding a move()
function that handles every move we make on the board:
def move(self, board, square, force=False):
for i in board.squares:
i.highlight = False
if square in self.get_valid_moves(board) or force:
prev_square = board.get_square_from_pos(self.pos)
self.pos, self.x, self.y = square.pos, square.x, square.y
prev_square.occupying_piece = None
square.occupying_piece = self
board.selected_piece = None
self.has_moved = True
# Pawn promotion
if self.notation == ' ':
if self.y == 0 or self.y == 7:
from data.classes.pieces.Queen import Queen
square.occupying_piece = Queen(
(self.x, self.y),
self.color,
board
)
# Move rook if king castles
if self.notation == 'K':
if prev_square.x - self.x == 2:
rook = board.get_piece_from_pos((0, self.y))
rook.move(board, board.get_square_from_pos((3, self.y)), force=True)
elif prev_square.x - self.x == -2:
rook = board.get_piece_from_pos((7, self.y))
rook.move(board, board.get_square_from_pos((5, self.y)), force=True)
return True
else:
board.selected_piece = None
return False
# True for all pieces except pawn
def attacking_squares(self, board):
return self.get_moves(board)
It takes board
and square
as arguments. If the tile we select to move our chosen piece is in self.get_valid_moves()
, the move is valid to execute. To make it happen, the move()
function will get the current square using board.get_square_from_pos(self.pos)
and save it in prev_square
and get its positions square.pos
, square.x
, square.y
and save it in self.pos
, self.x
, and self.y
for further use.
Then the function will empty the prev_square
, and the piece (self - the current chess piece class) will be moved to the square.occupying_piece
.
Chess has cool features; some of them are castling and pawn promotion, and that's what we do next.
If the notation of the piece we've just moved in is ' '
, which is a pawn, and it reaches row 0 (for white pawns) or row 7 (for black pawns), the pawn will be replaced by another queen of the same color.
And if the piece's notation is 'K'
and then moved 2 tiles to the left or right, it means the player's move is casting.
Making a Class for Each Piece
Now that we've finished the Square
, Board
, and Piece
classes, it's time to create different classes for every piece type. Each piece will have the main Piece
class as its parent class:
# /* Pawn.py
import pygame
from data.classes.Piece import Piece
class Pawn(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_pawn.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 35, board.tile_height - 35))
self.notation = ' '
def get_possible_moves(self, board):
output = []
moves = []
# move forward
if self.color == 'white':
moves.append((0, -1))
if not self.has_moved:
moves.append((0, -2))
elif self.color == 'black':
moves.append((0, 1))
if not self.has_moved:
moves.append((0, 2))
for move in moves:
new_pos = (self.x, self.y + move[1])
if new_pos[1] < 8 and new_pos[1] >= 0:
output.append(
board.get_square_from_pos(new_pos)
)
return output
def get_moves(self, board):
output = []
for square in self.get_possible_moves(board):
if square.occupying_piece != None:
break
else:
output.append(square)
if self.color == 'white':
if self.x + 1 < 8 and self.y - 1 >= 0:
square = board.get_square_from_pos(
(self.x + 1, self.y - 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
if self.x - 1 >= 0 and self.y - 1 >= 0:
square = board.get_square_from_pos(
(self.x - 1, self.y - 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
elif self.color == 'black':
if self.x + 1 < 8 and self.y + 1 < 8:
square = board.get_square_from_pos(
(self.x + 1, self.y + 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
if self.x - 1 >= 0 and self.y + 1 < 8:
square = board.get_square_from_pos(
(self.x - 1, self.y + 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
return output
def attacking_squares(self, board):
moves = self.get_moves(board)
# return the diagonal moves
return [i for i in moves if i.x != self.x]
Here is our code for the Pawn
pieces, whether it is black or white. As you notice, we have get_moves()
and attacking_square()
functions here in the Pawn
class, just like the functions we have in the Piece
class but given with a different script. It is because pawn pieces are basically allowed to move 1 step at a time away from their team position. A pawn also has 3 possible moves; the pawn can move up to 2 tiles from its starting position only, can move 1 step forward at a time, and can capture a piece 1 diagonally step at a time.
As we noticed, we have another function which is the get_possible_moves()
. As of its name, it gets all the possible moves of a piece base on the current state of the board.
Now let's move to do the other codes for other pieces.
Code for Knight.py
:
# /* Kinght.py
import pygame
from data.classes.Piece import Piece
class Knight(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_knight.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'N'
def get_possible_moves(self, board):
output = []
moves = [
(1, -2),
(2, -1),
(2, 1),
(1, 2),
(-1, 2),
(-2, 1),
(-2, -1),
(-1, -2)
]
for move in moves:
new_pos = (self.x + move[0], self.y + move[1])
if (
new_pos[0] < 8 and
new_pos[0] >= 0 and
new_pos[1] < 8 and
new_pos[1] >= 0
):
output.append([
board.get_square_from_pos(
new_pos
)
])
return output
Code for Bishop.py
:
# /* Bishop.py
import pygame
from data.classes.Piece import Piece
class Bishop(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_bishop.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'B'
def get_possible_moves(self, board):
output = []
moves_ne = []
for i in range(1, 8):
if self.x + i > 7 or self.y - i < 0:
break
moves_ne.append(board.get_square_from_pos(
(self.x + i, self.y - i)
))
output.append(moves_ne)
moves_se = []
for i in range(1, 8):
if self.x + i > 7 or self.y + i > 7:
break
moves_se.append(board.get_square_from_pos(
(self.x + i, self.y + i)
))
output.append(moves_se)
moves_sw = []
for i in range(1, 8):
if self.x - i < 0 or self.y + i > 7:
break
moves_sw.append(board.get_square_from_pos(
(self.x - i, self.y + i)
))
output.append(moves_sw)
moves_nw = []
for i in range(1, 8):
if self.x - i < 0 or self.y - i < 0:
break
moves_nw.append(board.get_square_from_pos(
(self.x - i, self.y - i)
))
output.append(moves_nw)
return output
Code for Rook.py
:
# /* Rook.py
import pygame
from data.classes.Piece import Piece
class Rook(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_rook.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'R'
def get_possible_moves(self, board):
output = []
moves_north = []
for y in range(self.y)[::-1]:
moves_north.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_north)
moves_east = []
for x in range(self.x + 1, 8):
moves_east.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_east)
moves_south = []
for y in range(self.y + 1, 8):
moves_south.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_south)
moves_west = []
for x in range(self.x)[::-1]:
moves_west.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_west)
return output
Code for Queen.py
:
# /* Queen.py
import pygame
from data.classes.Piece import Piece
class Queen(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_queen.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'Q'
def get_possible_moves(self, board):
output = []
moves_north = []
for y in range(self.y)[::-1]:
moves_north.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_north)
moves_ne = []
for i in range(1, 8):
if self.x + i > 7 or self.y - i < 0:
break
moves_ne.append(board.get_square_from_pos(
(self.x + i, self.y - i)
))
output.append(moves_ne)
moves_east = []
for x in range(self.x + 1, 8):
moves_east.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_east)
moves_se = []
for i in range(1, 8):
if self.x + i > 7 or self.y + i > 7:
break
moves_se.append(board.get_square_from_pos(
(self.x + i, self.y + i)
))
output.append(moves_se)
moves_south = []
for y in range(self.y + 1, 8):
moves_south.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_south)
moves_sw = []
for i in range(1, 8):
if self.x - i < 0 or self.y + i > 7:
break
moves_sw.append(board.get_square_from_pos(
(self.x - i, self.y + i)
))
output.append(moves_sw)
moves_west = []
for x in range(self.x)[::-1]:
moves_west.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_west)
moves_nw = []
for i in range(1, 8):
if self.x - i < 0 or self.y - i < 0:
break
moves_nw.append(board.get_square_from_pos(
(self.x - i, self.y - i)
))
output.append(moves_nw)
return output
Code for King.py
:
# /* King.py
import pygame
from data.classes.Piece import Piece
class King(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_king.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'K'
def get_possible_moves(self, board):
output = []
moves = [
(0,-1), # north
(1, -1), # ne
(1, 0), # east
(1, 1), # se
(0, 1), # south
(-1, 1), # sw
(-1, 0), # west
(-1, -1), # nw
]
for move in moves:
new_pos = (self.x + move[0], self.y + move[1])
if (
new_pos[0] < 8 and
new_pos[0] >= 0 and
new_pos[1] < 8 and
new_pos[1] >= 0
):
output.append([
board.get_square_from_pos(
new_pos
)
])
return output
def can_castle(self, board):
if not self.has_moved:
if self.color == 'white':
queenside_rook = board.get_piece_from_pos((0, 7))
kingside_rook = board.get_piece_from_pos((7, 7))
if queenside_rook != None:
if not queenside_rook.has_moved:
if [
board.get_piece_from_pos((i, 7)) for i in range(1, 4)
] == [None, None, None]:
return 'queenside'
if kingside_rook != None:
if not kingside_rook.has_moved:
if [
board.get_piece_from_pos((i, 7)) for i in range(5, 7)
] == [None, None]:
return 'kingside'
elif self.color == 'black':
queenside_rook = board.get_piece_from_pos((0, 0))
kingside_rook = board.get_piece_from_pos((7, 0))
if queenside_rook != None:
if not queenside_rook.has_moved:
if [
board.get_piece_from_pos((i, 0)) for i in range(1, 4)
] == [None, None, None]:
return 'queenside'
if kingside_rook != None:
if not kingside_rook.has_moved:
if [
board.get_piece_from_pos((i, 0)) for i in range(5, 7)
] == [None, None]:
return 'kingside'
def get_valid_moves(self, board):
output = []
for square in self.get_moves(board):
if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
output.append(square)
if self.can_castle(board) == 'queenside':
output.append(
board.get_square_from_pos((self.x - 2, self.y))
)
if self.can_castle(board) == 'kingside':
output.append(
board.get_square_from_pos((self.x + 2, self.y))
)
return output
Let's finish the game by adding code in main.py
that runs our whole game:
import pygame
from data.classes.Board import Board
pygame.init()
WINDOW_SIZE = (600, 600)
screen = pygame.display.set_mode(WINDOW_SIZE)
board = Board(WINDOW_SIZE[0], WINDOW_SIZE[1])
def draw(display):
display.fill('white')
board.draw(display)
pygame.display.update()
if __name__ == '__main__':
running = True
while running:
mx, my = pygame.mouse.get_pos()
for event in pygame.event.get():
# Quit the game if the user presses the close button
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# If the mouse is clicked
if event.button == 1:
board.handle_click(mx, my)
if board.is_in_checkmate('black'): # If black is in checkmate
print('White wins!')
running = False
elif board.is_in_checkmate('white'): # If white is in checkmate
print('Black wins!')
running = False
# Draw the board
draw(screen)
As you see above, we had screen
and board
variable, which has pretty similar arguments but not really.
The screen
handles the rendering of the chess board on the screen so we can see what's happening in the board
. The code pygame.display.set_mode(WINDOW_SIZE)
creates the game window.
While we use the board
for making and handling tiles, tile positions, and what piece a chess square has. As you remember, in the Board
class code, we give it two arguments: the game window's length and width.
To keep the game running, we give it a while
loop that runs as long as the value of running
is True
.
The mx, my = pygame.mouse.get_pos()
locates the current position of your mouse as long as it's inside the game window. If you add print(mx, my)
below this code, you'll see the current mouse position, and its value changes every time you hover it inside the window.
The event.type == pygame.MOUSEBUTTONDOWN
catches every click you make. To identify if a player is making a move, every time it catches a player doing a click, the current position of the mouse we get from pygame.mouse.get_pos()
will be sent in Board.handle_click()
, and process your click back there.
Ok, now let's try this game. If it's working so in your terminal, move to the directory where our Main.py
file was saved, then run the Main.py
. Once you run the file, the game will start immediately:
Start clicking on the pieces that can move, and you'll see available moves:
To simplify it, always remember that the chess game has two main parts, the board, and the pieces.
The board is in charge of every tile's name & position and the rules of the game, while the piece classes take care of moves and attacks for every piece.
To make the board, you should have a Square
class that creates the handling chess tiles that also mind the piece it contains, and another class called Board
, which contains the game rules. We also need to do class for every chess piece, from the Pawn
to the King
. And that's how you make a chess game with Python using only classes and pygame
!
You can check the complete code here.
Here is a list of other pygame tutorials:
Happy coding ♥
Take the stress out of learning Python. Meet our Python Code Assistant – your new coding buddy. Give it a whirl!
View Full Code Understand My Code
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!