Ever stared at a Sudoku puzzle and thought, "I could totally make one of these with Python..."? Well, you're about to. If you’ve got Python and a bit of curiosity, you absolutely can. This article walks you through building a complete Sudoku puzzle generator and solver.
No third-party packages, no setup, no fluff. Just pure Python and a whole lot of logic fun.
What Are We Creating?
By the time we’re done, you'll have a Python script that not only creates fully valid Sudoku boards from scratch but also removes just the right amount of numbers to make a playable puzzle and then solves it too.
It even supports difficulty levels like easy, medium, and hard, and prints the whole thing out in a clean, readable format. It’s one of those rare projects that's as satisfying to see run as it is to build.
The Game Plan
Here’s how we’ll break it down:
- Start with generating a valid, fully filled Sudoku board.
- Remove cells to create the actual puzzle, with difficulty baked in.
- Add a backtracking algorithm to solve the puzzle.
- Print everything nicely to the console so it’s easy on the eyes.
No black-box libraries. Just logic and clean code.
Quick Sudoku Refresher
If you’ve ever played Sudoku, you already know the basics. But let’s recap for clarity: a Sudoku board is a 9×9 grid split into nine 3×3 boxes. The rules are simple—but strict.
Each row, column, and 3×3 box must contain the digits 1 through 9, with no repeats. And a proper puzzle must have exactly one unique solution.
Let’s Talk Code
Here’s the entire Python script you’ll need. It's broken down below so we can walk through how it all works, but feel free to run it as-is to see the magic happen:
import random
import copy
# Constants for board dimensions
BOARD_SIZE = 9
BOX_SIZE = 3 # Size of the smaller 3x3 boxes
# Check if placing 'num' at board[row][col] is valid according to Sudoku rules
def is_valid(board, row, col, num):
# Check the row and column
for i in range(BOARD_SIZE):
if board[row][i] == num or board[i][col] == num:
return False
# Check the 3x3 box the cell belongs to
box_row_start = (row // BOX_SIZE) * BOX_SIZE
box_col_start = (col // BOX_SIZE) * BOX_SIZE
for i in range(BOX_SIZE):
for j in range(BOX_SIZE):
if board[box_row_start + i][box_col_start + j] == num:
return False
return True # No conflicts found
# Solve the Sudoku using backtracking
def solve(board):
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
if board[row][col] == 0: # Empty cell
for num in range(1, 10): # Try numbers 1-9
if is_valid(board, row, col, num):
board[row][col] = num
if solve(board):
return True # Puzzle solved!
board[row][col] = 0 # Backtrack if not solvable
return False # No valid number found, backtrack
return True # No empty cells left; puzzle is solved
# Create a full, valid Sudoku board
def generate_full_board():
board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] # Start with all zeros
fill_board(board)
return board
# Recursive function to fill the board with valid numbers
def fill_board(board):
numbers = list(range(1, 10)) # Numbers to place
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
if board[row][col] == 0:
random.shuffle(numbers) # Randomize number order for variety
for num in numbers:
if is_valid(board, row, col, num):
board[row][col] = num
if fill_board(board):
return True # Keep going recursively
board[row][col] = 0 # Backtrack
return False # Trigger backtracking if stuck
return True # Board successfully filled
# Remove cells from the board to create the puzzle
def remove_cells(board, num_holes):
holes = 0
while holes < num_holes:
row = random.randint(0, 8)
col = random.randint(0, 8)
if board[row][col] != 0:
board[row][col] = 0 # Remove cell
holes += 1
return board
# Print the board in a clean, readable format
def print_board(board):
for i in range(BOARD_SIZE):
row = ''
for j in range(BOARD_SIZE):
val = board[i][j]
row += '.' if val == 0 else str(val) # Use '.' for empty cells
row += ' '
if j % 3 == 2 and j != 8: # Add vertical dividers
row += '| '
print(row)
if i % 3 == 2 and i != 8: # Add horizontal dividers
print('-' * 21)
# Generate a puzzle based on difficulty level
def generate_puzzle(difficulty='medium'):
full_board = generate_full_board() # Generate completed board
# Map difficulty to number of cells to remove
difficulty_map = {
'easy': 30,
'medium': 40,
'hard': 55
}
holes = difficulty_map.get(difficulty.lower(), 40) # Default to medium
puzzle = remove_cells(copy.deepcopy(full_board), num_holes=holes)
return puzzle, full_board # Return the puzzle and the complete solution
# ===== Main Logic =====
difficulty = 'hard' # Choose difficulty: 'easy', 'medium', or 'hard'
print(f"Difficulty: {difficulty.capitalize()}\n")
# Generate puzzle and get its full solution
puzzle, solution_template = generate_puzzle(difficulty)
print("Generated Sudoku Puzzle:\n")
print_board(puzzle) # Show puzzle with missing numbers
# Try to solve the puzzle
solution = copy.deepcopy(puzzle)
if solve(solution):
print("\nSolved Sudoku Board:\n")
print_board(solution)
else:
print("\nNo solution found.")

Join the conversation! Your thoughts help the community grow.