Sudoku Game Development with Python and Pygame

PythonPythonIntermediate
Practice Now

Introduction

In this project, we will create a Sudoku game using Python and the Pygame library. The game will generate a Sudoku grid of the specified difficulty level and let players solve the puzzle by filling in the empty cells with numbers. The game will provide features like selecting difficulty, highlighting selected cells, and checking if the grid is complete.

👀 Preview

Sudoku Game Preview

🎯 Tasks

In this project, you will learn:

  • How to import required libraries
  • How to initialize PyGame
  • How to define colors
  • How to set game window dimensions and title
  • How to create the game window
  • How to load fonts
  • How to generate a Sudoku grid
  • How to solve the Sudoku grid using backtracking algorithm
  • How to remove numbers from the grid based on difficulty
  • How to draw the Sudoku grid on the game window
  • How to check if the grid is fully filled
  • How to get the cell coordinates under mouse position
  • How to select the difficulty level
  • How to implement the main game loop

🏆 Achievements

After completing this project, you will be able to:

  • Use the Pygame library for game development in Python
  • Generate a Sudoku grid of a specified difficulty level
  • Solve a Sudoku grid using the backtracking algorithm
  • Handle mouse and keyboard events in Pygame
  • Draw shapes and text on the game window
  • Implement the main game loop in Pygame

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/FileHandlingGroup(["`File Handling`"]) pygame(("`Pygame`")) -.-> pygame/CoreConceptsGroup(["`Core Concepts`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/AdvancedTopicsGroup(["`Advanced Topics`"]) python(("`Python`")) -.-> python/PythonStandardLibraryGroup(["`Python Standard Library`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") python/FileHandlingGroup -.-> python/with_statement("`Using with Statement`") pygame/CoreConceptsGroup -.-> pygame/display("`Display Management`") pygame/CoreConceptsGroup -.-> pygame/draw("`Drawing Utilities`") pygame/CoreConceptsGroup -.-> pygame/event("`Event Handling`") pygame/CoreConceptsGroup -.-> pygame/font("`Font Styling`") pygame/CoreConceptsGroup -.-> pygame/key("`Keyboard Interaction`") pygame/CoreConceptsGroup -.-> pygame/mouse("`Mouse Functions`") python/BasicConceptsGroup -.-> python/variables_data_types("`Variables and Data Types`") python/BasicConceptsGroup -.-> python/strings("`Strings`") python/BasicConceptsGroup -.-> python/booleans("`Booleans`") python/BasicConceptsGroup -.-> python/type_conversion("`Type Conversion`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/ControlFlowGroup -.-> python/while_loops("`While Loops`") python/ControlFlowGroup -.-> python/list_comprehensions("`List Comprehensions`") python/DataStructuresGroup -.-> python/lists("`Lists`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/dictionaries("`Dictionaries`") python/DataStructuresGroup -.-> python/sets("`Sets`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/standard_libraries("`Common Standard Libraries`") python/AdvancedTopicsGroup -.-> python/iterators("`Iterators`") python/PythonStandardLibraryGroup -.-> python/math_random("`Math and Random`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/with_statement -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/display -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/draw -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/event -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/font -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/key -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} pygame/mouse -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/variables_data_types -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/strings -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/booleans -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/type_conversion -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/conditional_statements -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/for_loops -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/while_loops -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/list_comprehensions -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/lists -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/tuples -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/dictionaries -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/sets -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/function_definition -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/importing_modules -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/standard_libraries -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/iterators -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/math_random -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} python/build_in_functions -.-> lab-298862{{"`Sudoku Game Development with Python and Pygame`"}} end

Create the Project Files

To begin, create a file named sudoku_game.py and open it in your preferred text editor or integrated development environment (IDE).

cd ~/project
touch sudoku_game.py

Import Required Libraries

Import the required libraries at the beginning of the file. We need the pygame and random libraries for this game.

import pygame
import random

Install the pygame library using the pip command.

sudo pip install pygame

Initialize PyGame

Initialize the Pygame library to set up the game window.

pygame.init()

Define Colors

Define the colors to be used in the game. We will use the RGB format for colors.

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (200, 200, 200)
BLUE = (0, 0, 255)
LIGHT_BLUE = (100, 100, 255)

Set Game Window Dimensions and Title

Set the dimensions of the game window and set a title for the game.

WINDOW_SIZE = (550, 550)
CELL_SIZE = WINDOW_SIZE[0] // 9

pygame.display.set_caption("Sudoku Game")

Create the Game Window

Create the game window using the specified dimensions.

screen = pygame.display.set_mode(WINDOW_SIZE)

Load Fonts

We will need to load fonts to display numbers on the game screen. Load two fonts, one for the larger numbers and one for the smaller numbers.

font_large = pygame.font.SysFont("calibri", 50)
font_small = pygame.font.SysFont("calibri", 30)

Generate Sudoku Grid

Create a function generate_sudoku(difficulty) that generates a new Sudoku grid of the specified difficulty level. The function should return the generated grid.

def generate_sudoku(difficulty):
    ## Function code goes here
    pass

Fill Diagonal Subgrids

Inside the generate_sudoku function, fill the diagonal subgrids of the grid with random numbers. This ensures that each subgrid contains numbers from 1 to 9 without repetition.

## Fill diagonal subgrids
for i in range(0, 9, 3):
    nums = random.sample(range(1, 10), 3)
    for j in range(3):
        grid[i + j][i + j] = nums[j]

Solve Sudoku Grid

Create a function solve_sudoku(grid) that solves the Sudoku grid using the backtracking algorithm. The function should return True if the grid is solvable and False otherwise.

def solve_sudoku(grid):
    ## Function code goes here
    pass

Find Empty Cell

Inside the solve_sudoku function, create a helper function find_empty_cell(grid) that returns the coordinates of the next empty cell in the grid. If there are no empty cells, return None.

def find_empty_cell(grid):
    for row in range(9):
        for col in range(9):
            if grid[row][col] == 0:
                return (row, col)

    return None

Check Valid Move

Inside the solve_sudoku function, create a helper function is_valid_move(grid, row, col, num) that checks if placing a number in a cell is a valid move. The function should return True if the move is valid and False otherwise.

def is_valid_move(grid, row, col, num):
    ## Function code goes here
    pass

Solve Sudoku Grid (Continued)

Inside the solve_sudoku function, use the helper functions find_empty_cell and is_valid_move to implement the backtracking algorithm. If a solution is found, return True. If the current number leads to an invalid solution, backtrack by setting the current cell to 0.

## Try filling the empty cell with numbers from 1 to 9
for num in range(1, 10):
    if is_valid_move(grid, row, col, num):
        grid[row][col] = num

        if solve_sudoku(grid):
            return True

        ## If the current number leads to an invalid solution, backtrack
        grid[row][col] = 0

return False

Remove Numbers Based on Difficulty

Inside the generate_sudoku function, remove numbers from the grid based on the specified difficulty level. The difficulty levels are: 1 (easy), 2 (medium), 3 (hard). The number of numbers to remove is calculated as num_to_remove = 45 + 10 * difficulty.

## Remove numbers based on difficulty level
num_to_remove = 45 + 10 * difficulty
for _ in range(num_to_remove):
    row = random.randint(0, 8)
    col = random.randint(0, 8)
    grid[row][col] = 0

return grid

Draw Grid

Create a function draw_grid(grid, selected_cell) that draws the Sudoku grid on the game window. This function should be responsible for drawing cells, numbers, and highlighting the selected cell.

def draw_grid(grid, selected_cell):
    ## Function code goes here
    pass

Fill Cells and Draw Numbers

Inside the draw_grid function, loop through the grid and draw the cells and numbers using the Pygame functions pygame.draw.rect and screen.blit.

## Draw the cells
for row in range(9):
    for col in range(9):
        cell_rect = pygame.Rect(
            col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE
        )
        pygame.draw.rect(screen, GRAY, cell_rect)

        ## Draw the numbers
        if grid[row][col] != 0:
            number = font_small.render(str(grid[row][col]), True, BLACK)
            text_rect = number.get_rect(
                center=(
                    col * CELL_SIZE + CELL_SIZE // 2,
                    row * CELL_SIZE + CELL_SIZE // 2,
                )
            )
            screen.blit(number, text_rect)

Highlight Selected Cell

Inside the draw_grid function, highlight the selected cell if it is not None using the pygame.draw.rect function.

## Highlight the selected cell
if (row, col) == selected_cell:
    pygame.draw.rect(screen, LIGHT_BLUE, cell_rect, 3)

Draw Grid Lines

Inside the draw_grid function, draw the lines to create the grid using the pygame.draw.line function.

## Draw the lines
for i in range(10):
    if i % 3 == 0:
        thickness = 4
    else:
        thickness = 1

    pygame.draw.line(
        screen,
        BLACK,
        (0, i * CELL_SIZE),
        (WINDOW_SIZE[0], i * CELL_SIZE),
        thickness,
    )
    pygame.draw.line(
        screen,
        BLACK,
        (i * CELL_SIZE, 0),
        (i * CELL_SIZE, WINDOW_SIZE[1]),
        thickness,
    )

Update the Display

Inside the draw_grid function, update the display using the pygame.display.update function.

pygame.display.update()

Check If Grid Is Full

Create a function is_grid_full(grid) that checks if the Sudoku grid is fully filled. This can be done by looping through the grid and checking if any cell contains 0.

def is_grid_full(grid):
    ## Function code goes here
    pass

Get Cell Under Mouse

Create a function get_cell_under_mouse(pos) that returns the cell coordinates under the mouse position. This can be calculated by dividing the mouse position by the cell size.

def get_cell_under_mouse(pos):
    ## Function code goes here
    pass

Select Difficulty

Create a function select_difficulty() that allows the player to select the difficulty level before starting the game. This function should display the difficulty options and return the selected difficulty level.

def select_difficulty():
    ## Function code goes here
    pass

Implement Difficulty Selection

Inside the select_difficulty function, implement the difficulty selection logic using the Pygame functions pygame.mouse.get_pos and pygame.event.get.

## Display difficulty selection text
title_text = font_large.render("Select Difficulty", True, BLACK)
screen.blit(title_text, (110, 200))

## Display difficulty options
option_y = 300
for difficulty, label in difficulties.items():
    option_text = font_small.render(f"{difficulty}. {label}", True, BLACK)
    text_rect = option_text.get_rect(center=(WINDOW_SIZE[0] // 2, option_y))
    screen.blit(option_text, text_rect)
    option_y += 70

pygame.display.update()

## Wait for difficulty selection
difficulty_selected = False
difficulty = 1

while not difficulty_selected:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            return
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:  ## Left mouse button
                pos = pygame.mouse.get_pos()
                if 180 <= pos[0] <= 380:
                    if 300 <= pos[1] <= 350:
                        difficulty = 1
                        difficulty_selected = True
                    elif 370 <= pos[1] <= 420:
                        difficulty = 2
                        difficulty_selected = True
                    elif 440 <= pos[1] <= 490:
                        difficulty = 3
                        difficulty_selected = True

return difficulty

Main Game Loop

Create the main game loop by defining a function main().

def main():
    ## Function code goes here
    pass

Select Difficulty and Generate Grid

Inside the main function, select the difficulty level by calling select_difficulty() and return if the difficulty is None. Then, generate a Sudoku grid using the selected difficulty level by calling generate_sudoku(difficulty).

## Select the difficulty level
difficulty = select_difficulty()
if difficulty is None:
    return

## Generate a new Sudoku grid
grid = generate_sudoku(difficulty)

Game Loop Variables

Inside the main function, create game loop variables selected_cell and running. Set selected_cell to None and running to True.

selected_cell = None
running = True

Enable Key Repeat

Inside the main function, enable key repeat using the pygame.key.set_repeat function. We set the delay and interval to 100 milliseconds to make it easier for players to fill cells.

## Enable key repeat
pygame.key.set_repeat(100, 100)

Handle Events

Inside the game loop, handle events using the pygame.event.get function. Check for quit events, mouse button events, and key events.

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.MOUSEBUTTONDOWN:
        ## Handle mouse button events
        pass
    elif event.type == pygame.KEYDOWN:
        ## Handle key events
        pass

Handle Mouse Button Events

Inside the event handling for mouse button events, check if the left mouse button is clicked or the right mouse button is clicked.

if event.button == 1:  ## Left mouse button
    ## Handle left mouse button click
    pass
elif event.button == 3:  ## Right mouse button
    ## Handle right mouse button click
    pass

Handle Left Mouse Button Click

Inside the left mouse button click handling, get the cell coordinates under the mouse position by calling get_cell_under_mouse(pos).

## Get the cell under the mouse position
pos = pygame.mouse.get_pos()
row, col = get_cell_under_mouse(pos)

Handle Left Mouse Button Click (Continued)

Inside the left mouse button click handling, select the cell if it's empty by checking if grid[row][col] == 0.

## Select the cell if it's empty
if grid[row][col] == 0:
    selected_cell = (row, col)

Handle Right Mouse Button Click

Inside the right mouse button click handling, clear the selected cell if it's not None and it's empty by checking if grid[selected_cell[0]][selected_cell[1]] == 0.

if selected_cell:
    ## Clear the selected cell if it's empty
    if grid[selected_cell[0]][selected_cell[1]] == 0:
        grid[selected_cell[0]][selected_cell[1]] = 0

Handle Key Events

Inside the event handling for key events, check which number key is pressed and set the value of the selected cell accordingly.

if selected_cell:
    ## Get the key pressed
    if event.key == pygame.K_1:
        grid[selected_cell[0]][selected_cell[1]] = 1
    elif event.key == pygame.K_2:
        grid[selected_cell[0]][selected_cell[1]] = 2
    elif event.key == pygame.K_3:
        grid[selected_cell[0]][selected_cell[1]] = 3
    elif event.key == pygame.K_4:
        grid[selected_cell[0]][selected_cell[1]] = 4
    elif event.key == pygame.K_5:
        grid[selected_cell[0]][selected_cell[1]] = 5
    elif event.key == pygame.K_6:
        grid[selected_cell[0]][selected_cell[1]] = 6
    elif event.key == pygame.K_7:
        grid[selected_cell[0]][selected_cell[1]] = 7
    elif event.key == pygame.K_8:
        grid[selected_cell[0]][selected_cell[1]] = 8
    elif event.key == pygame.K_9:
        grid[selected_cell[0]][selected_cell[1]] = 9

Draw Grid and Check Completion

Inside the game loop, draw the grid by calling draw_grid(grid, selected_cell). Then, check if the grid is complete by calling is_grid_full(grid).

## Draw the grid
draw_grid(grid, selected_cell)

## Check if the grid is complete
if is_grid_full(grid):
    print("Congratulations! You solved the Sudoku puzzle.")

Update Display and Quit

After the game loop, update the display using the pygame.display.update function and quit the game using pygame.quit().

## Update the display
pygame.display.update()

## Quit the game
pygame.quit()

Run the Game

Finally, add a condition to check if the current file is the main entry point of the program. If it is, run the game by calling the main() function.

if __name__ == "__main__":
    main()

Run the game using the python command.

python sudoku_game.py
Alt text

Summary

Congratulations! You have successfully created a Sudoku game using Python and the Pygame library. The game allows players to select a difficulty level, fill in the empty cells with numbers, and checks if the grid is complete. Have fun playing and solving Sudoku puzzles!

Other Python Tutorials you may like