Chess is a game that has captured minds for centuries, blending strategy and skill with every move. Whether you’re a seasoned player or just getting started, building a digital version of this classic game can be a fun and rewarding challenge.
Today, you’ll dive into building your very own chess game using Pygame in Python. We’ll take it step by step, from setting up the game board and loading the chess pieces to implementing both player vs player and AI modes. Along the way, we’ll also tackle key gameplay mechanics like handling checkmate and stalemate scenarios, so by the end, you’ll have a fully functional chess game ready to go!
Table of Contents
- Getting Started
- Setting Up Pygame
- Loading and Resizing Chess Piece Images
- Creating the Menu and Drawing the Chess Board
- Handling Highlights and Legal Moves
- Evaluating Moves, Piece Values, and Pawn Promotion
- Main Function
- Main Block
- Example
- Conclusion
- Full Code
Getting Started
Don’t forget to install these libraries to ensure everything runs smoothly!
$ pip install pygame
$ pip install python-chess
First, let’s bring in the tools we need. We’ll start by importing the libraries that make everything tick:
- Pygame helps us create the graphical interface where the chess game will play out.
- We use the chess library to handle board representation and move validation—it makes the chess logic much simpler.
- Finally, random will allow us to make random moves when necessary.
import pygame
import chess
import random
Setting Up Pygame
Now that we have our tools ready, let’s set up Pygame. This step essentially tells Pygame to get everything initialized and ready for use.
# Initialize Pygame
pygame.init()
Next, we need to create the window where the chess game will appear. We’ll define the size of the window (800×500 by default), make it resizable to fit any screen, and give it a distinct title so you know you’re playing “Chess – The Pycodes“.
# Set up display with resizable window
width, height = 800, 500 # Default size
window = pygame.display.set_mode((width, height), pygame.RESIZABLE)
pygame.display.set_caption("Chess - The Pycodes")
Loading and Resizing Chess Piece Images
It’s time to load the images of the chess pieces—after all, we can’t play without them! We’ll create an image dictionary to store all the chess piece images using their shorthand symbols. This way, we can easily reference them later in our game.
# Load images for pieces
images = {}
image_files = {
'p': 'images/p.png', # White pawn
'n': 'images/n.png', # White knight
'b': 'images/b.png', # White bishop
'r': 'images/r.png', # White rook
'q': 'images/q.png', # White queen
'k': 'images/k.png', # White king
'P1': 'images/P1.png', # Black pawn
'N1': 'images/N1.png', # Black knight
'B1': 'images/B1.png', # Black bishop
'R1': 'images/R1.png', # Black rook
'Q1': 'images/Q1.png', # Black queen
'K1': 'images/K1.png' # Black king
}
To make sure these pieces fit perfectly on the chessboard, we’ve added a load_images()
function. This function will load the images into our dictionary and resize them to match the square size of the board.
def load_images(square_size):
"""Load chess piece images into the images dictionary, scaled to the square size."""
for symbol, file in image_files.items():
try:
images[symbol] = pygame.transform.scale(pygame.image.load(file), (square_size, square_size))
except pygame.error as e:
print(f"Error loading image for {symbol}: {e}")
Creating the Menu and Drawing the Chess Board
With our game framework in place, we can now explore the exciting part of creating the menu screen, where players will choose their game options and set the stage for their chess adventure:
To start, let’s create a handy helper function called draw_text()
. This function will help us display text on the screen. It takes in the text we want to show, the font style, the color, and the position where we want it to appear. Pretty straightforward, right?
def draw_text(text, font, color, x, y):
"""Helper function to draw text on the screen."""
screen_text = font.render(text, True, color)
window.blit(screen_text, (x, y))
Next up, we have the menu_screen()
function. This is where the magic happens! Here, we’re setting up the menu that greets players and presents their options.
- First, we define the fonts we’ll use for the menu. We also set some initial variables, like
selected_mode
,selected_difficulty
, andselected_side
.
def menu_screen():
"""Menu screen to choose game options."""
font = pygame.font.Font(None, 60)
small_font = pygame.font.Font(None, 40)
selected_mode = None
selected_difficulty = 'medium'
selected_side = 'w'
- Inside a loop, we fill the window with a black background. Then, we call our
draw_text()
function to show the title “Chess Game” right at the top.
running = True
while running:
window.fill(pygame.Color("black"))
draw_text("Chess Game", font, pygame.Color("white"), 280, 50)
- Now, let’s get into the options! Here, we give players the choice between “Player vs Player” or “Player vs Computer” by drawing more text on the screen.
# Game mode options
draw_text("1. Player vs Player", small_font, pygame.Color("white"), 250, 150)
draw_text("2. Player vs Computer", small_font, pygame.Color("white"), 250, 200)
- If a player selects “Player vs Computer”, we show the current difficulty level and let them know how to change it. They can cycle through easy, medium, and hard by pressing a “D” key.
# Difficulty options (Only if Player vs Computer is selected)
if selected_mode == 'computer':
draw_text(f"Difficulty: {selected_difficulty.capitalize()}", small_font, pygame.Color("white"), 250, 250)
draw_text("Press D to toggle difficulty", small_font, pygame.Color("white"), 250, 300)
- Next, we allow players to choose their side. This means they can pick whether they want to play as White or Black pieces. The screen will show their current selection, and they can switch sides by pressing another “S” key.
# Side selection
draw_text(f"Your side: {'White' if selected_side == 'w' else 'Black'}", small_font, pygame.Color("white"), 250, 350)
draw_text("Press S to toggle side", small_font, pygame.Color("white"), 250, 400)
- Lastly, we let players know how to start the game by pressing the “ENTER” key. This keeps the excitement building!
draw_text("Press ENTER to start", small_font, pygame.Color("white"), 250, 500)
- Now, let’s listen for any player input. If they decide to quit, we’ll close the game. Otherwise, we handle key presses to set the selected mode, toggle difficulty, switch sides, and eventually exit the menu when they’re ready to play.
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
return None, None, None # Exit the game
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_1:
selected_mode = 'player'
elif event.key == pygame.K_2:
selected_mode = 'computer'
elif event.key == pygame.K_d and selected_mode == 'computer':
# Toggle difficulty
if selected_difficulty == 'easy':
selected_difficulty = 'medium'
elif selected_difficulty == 'medium':
selected_difficulty = 'hard'
else:
selected_difficulty = 'easy'
elif event.key == pygame.K_s:
# Toggle player side
selected_side = 'b' if selected_side == 'w' else 'w'
elif event.key == pygame.K_RETURN and selected_mode is not None:
running = False # Exit the menu
- Once we’ve wrapped up the menu, we refresh the display to show any updates and return the selected options. This ensures that everything flows seamlessly as players embark on their chess adventure!
pygame.display.flip()
return selected_mode, selected_difficulty, selected_side
Awesome! Now that you’ve chosen all your options from the menu, the game smoothly takes you to the main board display. This is where all the action happens! The draw_board()
function steps in, bringing the chessboard to life with a beautiful mix of white and gray squares. It makes sure each chess piece is placed just right, thanks to the chess library.
And here’s where it gets even cooler: the highlight_square()
function helps you see the last move made and the square you’ve selected. Plus, the get_custom_piece_symbol()
function grabs the right symbol for each piece (like ‘n’ for knight) and puts it exactly where it should go on the board. Everything is lined up for an exciting game ahead!
def draw_board(board, square_size, selected_square=None, last_move=None):
"""Draw the chess board and pieces, adjusting to the current square size."""
colors = [pygame.Color("white"), pygame.Color("gray")]
for row in range(8):
for col in range(8):
color = colors[(row + col) % 2]
pygame.draw.rect(window, color, pygame.Rect(col * square_size, row * square_size, square_size, square_size))
# Draw pieces on the board
piece = board.piece_at(chess.square(col, 7 - row))
if piece:
custom_symbol = get_custom_piece_symbol(piece)
piece_image = images.get(custom_symbol)
if piece_image:
window.blit(piece_image, (col * square_size, row * square_size))
# Highlight the last move (from and to squares)
if last_move:
highlight_square(last_move.from_square, square_size, color="blue")
highlight_square(last_move.to_square, square_size, color="blue")
# Highlight the selected square
if selected_square is not None:
highlight_square(selected_square, square_size)
Handling Highlights and Legal Moves
Once the menu and chess board are established, we can now focus on how we handle highlights and legal moves to elevate the gameplay experience.
So, to give players feedback on their actions in the chess game, nothing beats some good highlights! To achieve this, we’ve created a few functions, each serving a specific purpose:
- The first one is
highlight_square()
, which highlights the selected square. It retrieves the square coordinates of the pressed piece usingchess.square_file()
andchess.square_rank()
. Once it has that information, it draws a bright yellow rectangle on that square with a border width of 3 usingpygame.draw.rect()
.
def highlight_square(square, square_size, color="yellow"):
"""Highlight the selected square."""
col, row = chess.square_file(square), 7 - chess.square_rank(square)
pygame.draw.rect(window, pygame.Color(color), pygame.Rect(col * square_size, row * square_size, square_size, square_size), 3)
- Next up is our second function,
highlight_check()
. This one adds a bit of drama and a sense of danger to the game. It keeps a close eye on the king’s square while also checking whose turn it is to play. If it finds out that the king is in check, thanks to theboard.is_check()
function, it calls thehighlight_square()
function to highlight that square in a bold red.
def highlight_check(board, square_size):
"""Highlight the king if in check."""
if board.is_check():
king_square = board.king(board.turn)
highlight_square(king_square, square_size, color="red")
- Then we have the third function,
highlight_legal_moves()
, which is pretty self-explanatory by its name! Essentially, it usesboard.legal_moves
to gather all the possible moves for the current player. It iterates through these moves to find those associated with the selected square and highlights them with a blue circle at the center of each square usingpygame.draw.circle()
.
def highlight_legal_moves(board, selected_square, square_size):
"""Highlight legal moves for the selected piece."""
legal_moves = board.legal_moves
for move in legal_moves:
if move.from_square == selected_square:
row = 7 - chess.square_rank(move.to_square)
col = chess.square_file(move.to_square)
pygame.draw.circle(window, pygame.Color("blue"), (col * square_size + square_size // 2, row * square_size + square_size // 2), square_size // 6)
By combining these three functions, we’re able to make this chess game not just playable but also an immersive experience!
Evaluating Moves, Piece Values, and Pawn Promotion
Custom Piece Symbols and Assigning Piece Values
In a regular chess game, notation is typically based on lowercase letters like ‘n’ or ‘p’ for white pieces, and uppercase letters like ‘Q’ or ‘K’ for black pieces. But here, we decided to add a unique twist to the black pieces’ symbols by appending ‘1’ to their notation. So, for example, ‘Q’ becomes ‘Q1’, adding a bit of flair. We do this using the get_custom_piece_symbol()
function. Because we can, why not?
def get_custom_piece_symbol(piece):
"""Map python-chess piece symbols to custom notation."""
symbol = piece.symbol()
if piece.color == chess.WHITE:
return symbol.lower()
else:
return symbol.upper() + '1'
Now that the symbols are customized, the next task is to assign values to each piece. In chess, pieces like the Queen or Rook are more valuable than pawns. We map each piece to its value using the piece_value()
function. This dictionary will help the computer understand how important each piece is when evaluating the board.
def piece_value(piece):
"""Assign a value to each piece type."""
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000 # King has an artificially high value to avoid danger
}
return piece_values.get(piece.piece_type, 0)
Board Evaluation and Threat Detection
A crucial aspect of chess is evaluating the material on the board—this helps in determining which side has the upper hand. We created the evaluate_board()
function to assess the current state of the board. It calculates the total evaluation score by summing up the values of the white pieces and subtracting the values of the black pieces.
If the score is positive, it means white is leading; if it’s negative, black has the advantage. The king has a high value (1000) since losing it results in a loss of the game.
def evaluate_board(board):
"""A simple evaluation function that sums up the values of pieces on the board."""
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000
}
score = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
value = piece_values.get(piece.piece_type, 0)
if piece.color == chess.WHITE:
score += value # Add value for White pieces
else:
score -= value # Subtract value for Black pieces
return score
Besides material evaluation, protecting pieces from being threatened is essential. The is_piece_threatened()
function helps in checking whether a piece on a particular square is under attack. It uses board.attackers()
from the python-chess library to detect threats.
def is_piece_threatened(board, square):
"""Check if a piece on a given square is attacked by the opponent."""
attackers = board.attackers(not board.piece_at(square).color, square)
return bool(attackers)
Move Evaluation
The next step is to determine whether a move is good or bad, as this can turn the tide of the game. The evaluate_move()
function evaluates a move by simulating it on the board with board.push()
, and then calling evaluate_board()
to assess the new board state.
If a valuable opponent’s piece is captured, the score increases. On the other hand, if the move exposes your piece to danger, the score decreases. After this analysis, the function uses board.pop()
to undo the move, leaving the game state unchanged.
def evaluate_move(board, move):
"""Evaluate the value of a move based on material gain/loss and board state."""
board.push(move)
# Basic evaluation by counting material (more complex evaluation can be added)
score = evaluate_board(board)
# Adjust score for different piece values
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000 # King is invaluable
}
# Prioritize capturing opponent pieces
captured_piece = board.piece_at(move.to_square)
if captured_piece:
score += piece_values.get(captured_piece.piece_type, 0) # Gain value from capture
# Penalize if moving a piece to a threatened square
moving_piece = board.piece_at(move.from_square)
if moving_piece and board.is_attacked_by(not board.turn, move.to_square):
score -= piece_values.get(moving_piece.piece_type, 0)
board.pop() # Undo the move
return score
Generating the Best Move
The heart of our chess AI is the computer_move()
function, which generates the best possible move for the computer. It starts by retrieving all legal moves, checks if the game is over, and then evaluates each move using evaluate_move()
.
If the computer plays white, it tries to maximize the score; if black, it minimizes the score. The function ultimately selects the best move or falls back on a random legal move if no move stands out.
def computer_move(board, difficulty):
"""Generate the best move for the computer, ensuring proper evaluation for both White and Black."""
legal_moves = list(board.legal_moves)
if board.is_game_over():
return None # No move if the game is over
best_move = None
if board.turn == chess.WHITE:
best_value = -float('inf') # White aims to maximize
else:
best_value = float('inf') # Black aims to minimize
for move in legal_moves:
move_value = evaluate_move(board, move)
if board.turn == chess.WHITE:
if move_value > best_value:
best_value = move_value
best_move = move
else:
if move_value < best_value:
best_value = move_value
best_move = move
# Fallback to a random move if no best move found
if not best_move:
best_move = random.choice(legal_moves)
return best_move
Pawn Promotion
Pawns, while undervalued, can become game-changers when they reach the end of the board. Our AI handles pawn promotion with the promote_pawn()
function. When a pawn reaches rank 7 (for white) or rank 0 (for black), it calls choose_promotion()
, allowing the player to promote the pawn to any piece they choose.
def promote_pawn(board, move):
"""Promote a pawn if it reaches the opposite end of the board."""
piece = board.piece_at(move.from_square)
# Check if the move is a pawn reaching the promotion rank
if piece and piece.piece_type == chess.PAWN:
promotion_rank_white = chess.square_rank(move.to_square) == 7
promotion_rank_black = chess.square_rank(move.to_square) == 0
if (piece.color == chess.WHITE and promotion_rank_white) or (
piece.color == chess.BLACK and promotion_rank_black):
return choose_promotion() # Prompt player for promotion choice
return None
The choose_promotion()
function provides the player with a choice of which piece they’d like to promote their pawn to, with a default promotion to Queen if no choice is made.
def choose_promotion():
"""Prompt the player to choose the promotion piece."""
font = pygame.font.Font(None, 40)
prompt = True
promotion_choice = None
while prompt:
window.fill(pygame.Color("black"))
draw_text("Choose Promotion Piece", font, pygame.Color("white"), 200, 200)
draw_text("Q: Queen, R: Rook, B: Bishop, N: Knight", font, pygame.Color("white"), 150, 300)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return chess.QUEEN # Default promotion to Queen if the game is quit
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
promotion_choice = chess.QUEEN
elif event.key == pygame.K_r:
promotion_choice = chess.ROOK
elif event.key == pygame.K_b:
promotion_choice = chess.BISHOP
elif event.key == pygame.K_n:
promotion_choice = chess.KNIGHT
if promotion_choice:
prompt = False
return promotion_choice
What Happens When the Game Ends?
When it’s time to bring the game to a close, we need to do it smoothly. That’s where the checkmate_screen()
function comes in. It takes a winner
argument so it knows how to display the game result. Two fonts are used here: a large one for showing who won or if it’s a draw, and a smaller one for instructions. Once the game reaches either a checkmate or stalemate, this function loops until the player decides what to do next.
def checkmate_screen(winner):
"""Display a screen when checkmate or stalemate occurs, showing the winner."""
font = pygame.font.Font(None, 80) # Larger font for results
small_font = pygame.font.Font(None, 40) # Smaller font for instructions
running = True
Next, the game window gets a black background, and depending on the game outcome, it displays the appropriate message. If it’s a stalemate, the message will say “It’s a Draw!” but if one player won by checkmate, the message will announce the winner. At the same time, the screen will also show instructions on how to restart or quit the game.
while running:
window.fill(pygame.Color("black")) # Set background to black
if winner == 'draw':
draw_text("Stalemate! It's a Draw!", font, pygame.Color("white"), 130, 200)
else:
draw_text(f"Checkmate! {winner} Wins!", font, pygame.Color("white"), 130, 200)
draw_text("Press R to Restart", small_font, pygame.Color("white"), 150, 400)
draw_text("Press Q to Quit", small_font, pygame.Color("white"), 150, 440)
The loop continues to listen for player actions. If the player presses the R key, the function will return True
, signaling that the game should restart. If they press the Q key, it returns False
, meaning the game should quit. If the player clicks the close button, the game will also exit.
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
return True # Restart game
elif event.key == pygame.K_q:
return False # Quit game
In the end, pygame.display.flip()
is used to make sure everything on the screen stays updated while waiting for player input. Once the player makes a choice, the loop ends, and the function returns False
, breaking out of the game loop.
pygame.display.flip()
return False # End the game if no action
Main Function
We have finally reached the core of our chess game, the one that manages everything the main()
function, let’s break it down :
- First, we kick things off by creating the main function, which is the heart of our chess game. It handles setting up the game window, managing the game loop, responding to player input, and checking for the endgame conditions like checkmate or stalemate.
def main():
Setting Up the Game Window
We start by setting up the main game window, defining its initial size (800×800 pixels), and allowing it to be resized. This is where the game will be displayed, and it’s made resizable for flexibility.
DEFAULT_WIDTH, DEFAULT_HEIGHT = 800, 800 # Default size
window = pygame.display.set_mode((DEFAULT_WIDTH, DEFAULT_HEIGHT), pygame.RESIZABLE)
pygame.display.set_caption("Chess - The Pycodes")
width, height = window.get_size()
square_size = width // 8
Loading the Chess Piece Images
Next, we load the chess pieces. We adjust their size to fit perfectly on the board, with each square’s size calculated by dividing the window width by 8.
load_images(square_size)
Choosing Game Mode and Setup
Here, we call the menu_screen()
function, which allows the player to choose the game mode (like Player vs. Computer) and set difficulty or which side to play. If the player exits from the menu, the game will quit before proceeding.
game_mode, difficulty, player_side = menu_screen()
board = chess.Board()
selected_square = None
last_move = None
running = True
if game_mode is None: # If the game is exited from the menu
return
Managing the Game Loop
Once everything is set up, we enter the main game loop, which controls the flow of the game. We adjust the square size dynamically in case the window is resized and ensure the board always fits neatly within the window.
clock = pygame.time.Clock()
while running:
width, height = window.get_size()
square_size = min(width, height) // 8 # Ensure the board fits in the window
Drawing the Board and Highlighting Moves
In every iteration of the loop, we call the draw_board()
function to render the board and pieces. We also highlight checks and any legal moves for the currently selected piece.
draw_board(board, square_size, selected_square, last_move)
highlight_check(board, square_size)
if selected_square:
highlight_legal_moves(board, selected_square, square_size)
pygame.display.flip()
Handling Player Input
Next, we handle the player’s input using pygame.event.get()
. If the player clicks on a square, the game checks if a piece is selected or if a move is attempted. We also check for window resizing or the player trying to quit the game.
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.VIDEORESIZE: # Handle window resizing
window = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # Left mouse click
col, row = event.pos[0] // square_size, event.pos[1] // square_size
clicked_square = chess.square(col, 7 - row)
Making a Move
Once the player selects a square, the game checks if a move is legal using board.legal_moves
. If the move is valid, it gets executed. If it’s invalid, the selection is cleared.
if selected_square is None:
if board.piece_at(clicked_square) and board.piece_at(clicked_square).color == board.turn:
selected_square = clicked_square
highlight_legal_moves(board, selected_square, square_size)
else:
move = chess.Move(selected_square, clicked_square, promotion=promote_pawn(board, chess.Move(selected_square, clicked_square)))
if move in board.legal_moves:
last_move = move
board.push(move)
selected_square = None
Checking for Endgame Conditions
Once a move is made, we check for game-ending conditions like checkmate or stalemate using board.is_checkmate()
and board.is_stalemate()
. If either occurs, the checkmate_screen()
function is triggered to display the result, and the player can choose to restart or quit.
if board.is_checkmate():
winner = "White" if board.turn == chess.BLACK else "Black"
if not checkmate_screen(winner):
running = False
else:
return main() # Restart game
elif board.is_stalemate():
if not checkmate_screen("draw"):
running = False
else:
return main() # Restart game
Computer’s Turn (For Player vs Computer Mode)
If the player is facing the computer, and it’s the computer’s turn, we simulate thinking with a short delay and let the computer make its move.
if game_mode == 'computer' and board.turn != (player_side == 'w'):
pygame.time.delay(1000) # Add delay to simulate thinking
move = computer_move(board, difficulty)
if move:
last_move = move
board.push(move)
Capping the Frame Rate
We cap the frame rate at 60 frames per second to ensure smooth gameplay.
clock.tick(60)
Quitting the Game
Finally, if the player quits the game, we call pygame.quit()
to close everything properly.
pygame.quit()
Main Block
if __name__ == "__main__":
main()
This part ensures that the code only runs when executed directly, and not when it’s imported as a module.
Example
You can find the chess pieces available for download on GitHub.
Conclusion
While this chess game code is relatively simple and not overly complex, it forms the backbone of any chess engine. Once you have these basic mechanics in place, you can add more advanced features like deeper AI logic, enhanced move evaluations, and more polished visuals or interactions. Overall, it serves as a great stepping stone.
Full Code
import pygame
import chess
import random
# Initialize Pygame
pygame.init()
# Set up display with resizable window
width, height = 800, 500 # Default size
window = pygame.display.set_mode((width, height), pygame.RESIZABLE)
pygame.display.set_caption("Chess - The Pycodes")
# Load images for pieces
images = {}
image_files = {
'p': 'images/p.png', # White pawn
'n': 'images/n.png', # White knight
'b': 'images/b.png', # White bishop
'r': 'images/r.png', # White rook
'q': 'images/q.png', # White queen
'k': 'images/k.png', # White king
'P1': 'images/P1.png', # Black pawn
'N1': 'images/N1.png', # Black knight
'B1': 'images/B1.png', # Black bishop
'R1': 'images/R1.png', # Black rook
'Q1': 'images/Q1.png', # Black queen
'K1': 'images/K1.png' # Black king
}
def load_images(square_size):
"""Load chess piece images into the images dictionary, scaled to the square size."""
for symbol, file in image_files.items():
try:
images[symbol] = pygame.transform.scale(pygame.image.load(file), (square_size, square_size))
except pygame.error as e:
print(f"Error loading image for {symbol}: {e}")
def draw_text(text, font, color, x, y):
"""Helper function to draw text on the screen."""
screen_text = font.render(text, True, color)
window.blit(screen_text, (x, y))
def menu_screen():
"""Menu screen to choose game options."""
font = pygame.font.Font(None, 60)
small_font = pygame.font.Font(None, 40)
selected_mode = None
selected_difficulty = 'medium'
selected_side = 'w'
running = True
while running:
window.fill(pygame.Color("black"))
draw_text("Chess Game", font, pygame.Color("white"), 280, 50)
# Game mode options
draw_text("1. Player vs Player", small_font, pygame.Color("white"), 250, 150)
draw_text("2. Player vs Computer", small_font, pygame.Color("white"), 250, 200)
# Difficulty options (Only if Player vs Computer is selected)
if selected_mode == 'computer':
draw_text(f"Difficulty: {selected_difficulty.capitalize()}", small_font, pygame.Color("white"), 250, 250)
draw_text("Press D to toggle difficulty", small_font, pygame.Color("white"), 250, 300)
# Side selection
draw_text(f"Your side: {'White' if selected_side == 'w' else 'Black'}", small_font, pygame.Color("white"), 250, 350)
draw_text("Press S to toggle side", small_font, pygame.Color("white"), 250, 400)
draw_text("Press ENTER to start", small_font, pygame.Color("white"), 250, 500)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
return None, None, None # Exit the game
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_1:
selected_mode = 'player'
elif event.key == pygame.K_2:
selected_mode = 'computer'
elif event.key == pygame.K_d and selected_mode == 'computer':
# Toggle difficulty
if selected_difficulty == 'easy':
selected_difficulty = 'medium'
elif selected_difficulty == 'medium':
selected_difficulty = 'hard'
else:
selected_difficulty = 'easy'
elif event.key == pygame.K_s:
# Toggle player side
selected_side = 'b' if selected_side == 'w' else 'w'
elif event.key == pygame.K_RETURN and selected_mode is not None:
running = False # Exit the menu
pygame.display.flip()
return selected_mode, selected_difficulty, selected_side
def draw_board(board, square_size, selected_square=None, last_move=None):
"""Draw the chess board and pieces, adjusting to the current square size."""
colors = [pygame.Color("white"), pygame.Color("gray")]
for row in range(8):
for col in range(8):
color = colors[(row + col) % 2]
pygame.draw.rect(window, color, pygame.Rect(col * square_size, row * square_size, square_size, square_size))
# Draw pieces on the board
piece = board.piece_at(chess.square(col, 7 - row))
if piece:
custom_symbol = get_custom_piece_symbol(piece)
piece_image = images.get(custom_symbol)
if piece_image:
window.blit(piece_image, (col * square_size, row * square_size))
# Highlight the last move (from and to squares)
if last_move:
highlight_square(last_move.from_square, square_size, color="blue")
highlight_square(last_move.to_square, square_size, color="blue")
# Highlight the selected square
if selected_square is not None:
highlight_square(selected_square, square_size)
def highlight_square(square, square_size, color="yellow"):
"""Highlight the selected square."""
col, row = chess.square_file(square), 7 - chess.square_rank(square)
pygame.draw.rect(window, pygame.Color(color), pygame.Rect(col * square_size, row * square_size, square_size, square_size), 3)
def highlight_check(board, square_size):
"""Highlight the king if in check."""
if board.is_check():
king_square = board.king(board.turn)
highlight_square(king_square, square_size, color="red")
def highlight_legal_moves(board, selected_square, square_size):
"""Highlight legal moves for the selected piece."""
legal_moves = board.legal_moves
for move in legal_moves:
if move.from_square == selected_square:
row = 7 - chess.square_rank(move.to_square)
col = chess.square_file(move.to_square)
pygame.draw.circle(window, pygame.Color("blue"), (col * square_size + square_size // 2, row * square_size + square_size // 2), square_size // 6)
def get_custom_piece_symbol(piece):
"""Map python-chess piece symbols to custom notation."""
symbol = piece.symbol()
if piece.color == chess.WHITE:
return symbol.lower()
else:
return symbol.upper() + '1'
def piece_value(piece):
"""Assign a value to each piece type."""
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000 # King has an artificially high value to avoid danger
}
return piece_values.get(piece.piece_type, 0)
def evaluate_board(board):
"""A simple evaluation function that sums up the values of pieces on the board."""
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000
}
score = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
value = piece_values.get(piece.piece_type, 0)
if piece.color == chess.WHITE:
score += value # Add value for White pieces
else:
score -= value # Subtract value for Black pieces
return score
def is_piece_threatened(board, square):
"""Check if a piece on a given square is attacked by the opponent."""
attackers = board.attackers(not board.piece_at(square).color, square)
return bool(attackers)
def evaluate_move(board, move):
"""Evaluate the value of a move based on material gain/loss and board state."""
board.push(move)
# Basic evaluation by counting material (more complex evaluation can be added)
score = evaluate_board(board)
# Adjust score for different piece values
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 1000 # King is invaluable
}
# Prioritize capturing opponent pieces
captured_piece = board.piece_at(move.to_square)
if captured_piece:
score += piece_values.get(captured_piece.piece_type, 0) # Gain value from capture
# Penalize if moving a piece to a threatened square, but first ensure the piece exists
moving_piece = board.piece_at(move.from_square)
if moving_piece and board.is_attacked_by(not board.turn, move.to_square):
score -= piece_values.get(moving_piece.piece_type, 0)
board.pop() # Undo the move
return score
def computer_move(board, difficulty):
"""Generate the best move for the computer, ensuring proper evaluation for both White and Black."""
legal_moves = list(board.legal_moves)
if board.is_game_over():
return None # No move if the game is over
best_move = None
if board.turn == chess.WHITE:
best_value = -float('inf') # White aims to maximize
else:
best_value = float('inf') # Black aims to minimize
for move in legal_moves:
move_value = evaluate_move(board, move)
if board.turn == chess.WHITE:
if move_value > best_value:
best_value = move_value
best_move = move
else:
if move_value < best_value:
best_value = move_value
best_move = move
# Fallback to a random move if no best move found
if not best_move:
best_move = random.choice(legal_moves)
return best_move
def promote_pawn(board, move):
"""Promote a pawn if it reaches the opposite end of the board."""
piece = board.piece_at(move.from_square)
# Check if the move is a pawn reaching the promotion rank
if piece and piece.piece_type == chess.PAWN:
promotion_rank_white = chess.square_rank(move.to_square) == 7
promotion_rank_black = chess.square_rank(move.to_square) == 0
if (piece.color == chess.WHITE and promotion_rank_white) or (
piece.color == chess.BLACK and promotion_rank_black):
return choose_promotion() # Prompt player for promotion choice
return None
def choose_promotion():
"""Prompt the player to choose the promotion piece."""
font = pygame.font.Font(None, 40)
prompt = True
promotion_choice = None
while prompt:
window.fill(pygame.Color("black"))
draw_text("Choose Promotion Piece", font, pygame.Color("white"), 200, 200)
draw_text("Q: Queen, R: Rook, B: Bishop, N: Knight", font, pygame.Color("white"), 150, 300)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return chess.QUEEN # Default promotion to Queen if the game is quit
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
promotion_choice = chess.QUEEN
elif event.key == pygame.K_r:
promotion_choice = chess.ROOK
elif event.key == pygame.K_b:
promotion_choice = chess.BISHOP
elif event.key == pygame.K_n:
promotion_choice = chess.KNIGHT
if promotion_choice:
prompt = False
return promotion_choice
def checkmate_screen(winner):
"""Display a screen when checkmate or stalemate occurs, showing the winner."""
font = pygame.font.Font(None, 80)
small_font = pygame.font.Font(None, 40)
running = True
while running:
window.fill(pygame.Color("black"))
if winner == 'draw':
draw_text("Stalemate! It's a Draw!", font, pygame.Color("white"), 130, 200)
else:
draw_text(f"Checkmate! {winner} Wins!", font, pygame.Color("white"), 130, 200)
draw_text("Press R to Restart", small_font, pygame.Color("white"), 150, 400)
draw_text("Press Q to Quit", small_font, pygame.Color("white"), 150, 440)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
return True # Restart game
elif event.key == pygame.K_q:
return False # Quit game
pygame.display.flip()
return False
def main():
# Get initial window size
# Initialize the window
DEFAULT_WIDTH, DEFAULT_HEIGHT = 800, 800 # Default size
window = pygame.display.set_mode((DEFAULT_WIDTH, DEFAULT_HEIGHT), pygame.RESIZABLE)
pygame.display.set_caption("Chess - The Pycodes")
width, height = window.get_size()
square_size = width // 8
# Load images based on square size
load_images(square_size)
# Show menu screen to choose game options
game_mode, difficulty, player_side = menu_screen()
board = chess.Board()
selected_square = None
last_move = None
running = True
if game_mode is None: # If the game is exited from the menu
return
# Set up the clock to manage frame rate
clock = pygame.time.Clock()
while running:
# Adjust square size dynamically based on current window dimensions
width, height = window.get_size()
square_size = min(width, height) // 8 # Ensure the board fits in the window
# Draw the board and update highlights
draw_board(board, square_size, selected_square, last_move)
highlight_check(board, square_size)
if selected_square:
highlight_legal_moves(board, selected_square, square_size)
pygame.display.flip()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.VIDEORESIZE: # Handle window resizing
window = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # Left mouse click
col, row = event.pos[0] // square_size, event.pos[1] // square_size
clicked_square = chess.square(col, 7 - row)
if selected_square is None:
# If no square is selected, select this square
if board.piece_at(clicked_square) and board.piece_at(clicked_square).color == board.turn:
selected_square = clicked_square
highlight_legal_moves(board, selected_square, square_size)
else:
# Try to move to the clicked square
move = chess.Move(selected_square, clicked_square, promotion=promote_pawn(board, chess.Move(selected_square, clicked_square)))
if move in board.legal_moves:
last_move = move
board.push(move)
selected_square = None
# Check for game end (checkmate or stalemate)
if board.is_checkmate():
winner = "White" if board.turn == chess.BLACK else "Black"
if not checkmate_screen(winner): # If player quits, exit loop
running = False
else:
return main() # Restart game
elif board.is_stalemate():
if not checkmate_screen("draw"): # If player quits, exit loop
running = False
else:
return main() # Restart game
else:
# If move is illegal, deselect the square
selected_square = None
# Computer move (for Player vs Computer mode)
if game_mode == 'computer' and board.turn != (player_side == 'w'):
pygame.time.delay(1000) # Add delay to simulate thinking
move = computer_move(board, difficulty)
if move:
last_move = move
board.push(move)
# Check for game end (checkmate or stalemate)
if board.is_checkmate():
winner = "White" if board.turn == chess.BLACK else "Black"
if not checkmate_screen(winner): # If player quits, exit loop
running = False
else:
return main() # Restart game
elif board.is_stalemate():
if not checkmate_screen("draw"): # If player quits, exit loop
running = False
else:
return main() # Restart game
# Cap frame rate at 60 FPS
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
main()
Happy Coding!