Make Your Own Sudoku Solver with Python

Hey there, aspiring coders and puzzle enthusiasts! Have you ever found yourself stuck on a tricky Sudoku puzzle, wishing you had a super-smart helper to crack it for you? Well, today, we’re going to build that helper ourselves using Python! It’s a fantastic way to learn some cool programming ideas while creating something genuinely useful (and fun!).

This project falls into the “Fun & Experiments” category because it’s a perfect blend of problem-solving and creative coding. By the end of this guide, you’ll have a working Sudoku solver and a better understanding of how computers can tackle complex puzzles.

What is Sudoku and How Does it Work?

First things first, what exactly is Sudoku?
Sudoku is a popular logic-based number placement puzzle. The goal is to fill a 9×9 grid with digits so that each column, each row, and each of the nine 3×3 subgrids (also called “boxes” or “regions”) contains all of the digits from 1 to 9. The puzzle starts with a partially completed grid, and your task is to fill in the rest.

  • Rule 1: Each row must contain all the numbers from 1 to 9.
  • Rule 2: Each column must contain all the numbers from 1 to 9.
  • Rule 3: Each of the nine 3×3 boxes must contain all the numbers from 1 to 9.

No number can be repeated within a single row, column, or 3×3 box. It sounds simple, but some puzzles can be surprisingly challenging!

The Brains Behind the Solver: Backtracking

To solve Sudoku with code, we’ll use a powerful technique called backtracking.
Think of backtracking like trying to navigate a maze. You go down one path. If it leads to a dead end, you “backtrack” to your last decision point and try another path. You keep doing this until you find the exit.

In Sudoku terms, our algorithm (which is just a fancy word for a step-by-step set of instructions a computer follows) will:
1. Find an empty spot on the board.
2. Try placing a number (from 1 to 9) in that spot.
3. Check if that number is valid (meaning it doesn’t break any Sudoku rules in its row, column, or 3×3 box).
4. If it’s valid, great! Move on to the next empty spot and repeat the process.
5. If it’s not valid, or if we get stuck later because no number works for a subsequent spot, we “backtrack.” This means we erase our last choice and try a different number in that spot. If no numbers work for a spot, we backtrack even further.

This might sound complicated, but Python makes it quite elegant!

Let’s Get Coding! Representing the Board

First, we need a way to represent our Sudoku board in Python. A simple and effective way is to use a list of lists. Imagine a list, and inside that list, there are nine other lists, each representing a row of the Sudoku board.

Here’s an example of a Sudoku board (0 represents an empty cell):

board = [
    [7,8,0,4,0,0,1,2,0],
    [6,0,0,0,7,5,0,0,9],
    [0,0,0,6,0,1,0,7,8],
    [0,0,7,0,4,0,2,6,0],
    [0,0,1,0,5,0,9,3,0],
    [9,0,4,0,6,0,0,0,5],
    [0,7,0,3,0,0,0,1,2],
    [1,2,0,0,0,7,4,0,0],
    [0,4,9,2,0,6,0,0,7]
]

We’ll also want a nice way to print the board so we can see our progress.

def print_board(board):
    """
    Prints the Sudoku board in a readable format.
    """
    for i in range(len(board)):
        if i % 3 == 0 and i != 0:
            print("- - - - - - - - - - - - ") # Separator for 3x3 boxes

        for j in range(len(board[0])):
            if j % 3 == 0 and j != 0:
                print(" | ", end="") # Separator for 3x3 boxes in a row

            if j == 8:
                print(board[i][j])
            else:
                print(str(board[i][j]) + " ", end="")

This print_board function (a reusable block of code that performs a specific task) will help us visualize the board with clear lines separating the 3×3 boxes.

Step 1: Finding an Empty Spot

Our solver needs to know where to put numbers. So, the first step is to find an empty cell, which we’ve represented with a 0.

def find_empty(board):
    """
    Finds an empty spot (represented by 0) on the board.
    Returns (row, col) of the empty spot, or None if no empty spots.
    """
    for r in range(len(board)):
        for c in range(len(board[0])):
            if board[r][c] == 0:
                return (r, c) # Returns a tuple (row, column)
    return None # No empty spots left

This find_empty function loops through each row (r) and each column (c) of the board. If it finds a 0, it immediately returns the position (r, c). If it goes through the whole board and finds no 0s, it means the board is full, and it returns None.

Step 2: Checking if a Number is Valid

This is the core logic for checking Sudoku rules. Before placing a number, we must ensure it doesn’t already exist in the same row, column, or 3×3 box.

def is_valid(board, num, pos):
    """
    Checks if placing 'num' at 'pos' (row, col) is valid according to Sudoku rules.
    """
    row, col = pos

    # 1. Check Row
    for c in range(len(board[0])):
        if board[row][c] == num and col != c:
            return False

    # 2. Check Column
    for r in range(len(board)):
        if board[r][col] == num and row != r:
            return False

    # 3. Check 3x3 Box
    # Determine which 3x3 box we are in
    box_start_r = (row // 3) * 3
    box_start_c = (col // 3) * 3

    for r in range(box_start_r, box_start_r + 3):
        for c in range(box_start_c, box_start_c + 3):
            if board[r][c] == num and (r, c) != pos:
                return False

    return True # If all checks pass, the number is valid

Let’s break down is_valid:
* pos is a tuple (row, col) indicating where we want to place num.
* Check Row: We loop through all columns in the current row. If num is found and it’s not at our current pos, it’s invalid.
* Check Column: We loop through all rows in the current col. If num is found and it’s not at our current pos, it’s invalid.
* Check 3×3 Box: This is a bit trickier. We figure out the starting row and column of the 3×3 box that pos belongs to using integer division (//). Then, we loop through that 3×3 box. If num is found (and it’s not at our current pos), it’s invalid.
* If none of these checks return False, it means num is safe to place, and the function returns True.

Step 3: The Main Solver (Backtracking in Action!)

Now for the main event: the solve function, which uses our find_empty and is_valid functions. This function will call itself repeatedly, which is a programming concept called recursion. It’s like a set of Russian dolls, where each doll contains a smaller version of itself.

def solve(board):
    """
    Solves the Sudoku board using the backtracking algorithm.
    Modifies the board in place.
    Returns True if a solution is found, False otherwise.
    """
    find = find_empty(board)
    if not find:
        return True # Base case: No empty spots means the board is solved!
    else:
        row, col = find # Get the position of the next empty spot

    for num in range(1, 10): # Try numbers from 1 to 9
        if is_valid(board, num, (row, col)):
            board[row][col] = num # If valid, place the number

            if solve(board): # Recursively call solve for the new board state
                return True # If the subsequent calls lead to a solution, we're done

            # If solve(board) returns False, it means our 'num' choice was wrong
            # Backtrack: reset the current spot to 0 and try the next number
            board[row][col] = 0

    return False # No number from 1-9 worked for this spot, so backtrack further

How solve works:
* Base Case: It first tries to find an empty spot. If find_empty returns None (meaning not find is True), it means the board is full, and we’ve successfully solved it! So, we return True.
* Recursive Step: If there’s an empty spot, we get its row and col.
* We then try numbers from 1 to 9 in that spot.
* For each num, we check if it’s is_valid.
* If is_valid returns True, we place num on the board.
* Crucially, we then call solve(board) again with the new board. This is the recursion! We’re asking, “Can this partially filled board be solved?”
* If that recursive call solve(board) also returns True, it means our current num choice led to a full solution, so we return True all the way up.
* Backtracking: If solve(board) returns False (meaning our num choice eventually led to a dead end), we remove num from the board (set it back to 0) and try the next number in the for num loop.
* If we try all numbers from 1 to 9 for a particular spot and none of them lead to a solution, it means our previous choices were wrong. In this case, solve returns False, telling the previous call to backtrack further.

Putting It All Together: The Full Code

Let’s combine all these functions into one Python script.

board = [
    [7,8,0,4,0,0,1,2,0],
    [6,0,0,0,7,5,0,0,9],
    [0,0,0,6,0,1,0,7,8],
    [0,0,7,0,4,0,2,6,0],
    [0,0,1,0,5,0,9,3,0],
    [9,0,4,0,6,0,0,0,5],
    [0,7,0,3,0,0,0,1,2],
    [1,2,0,0,0,7,4,0,0],
    [0,4,9,2,0,6,0,0,7]
]

def print_board(board):
    """
    Prints the Sudoku board in a readable format.
    """
    for i in range(len(board)):
        if i % 3 == 0 and i != 0:
            print("- - - - - - - - - - - - ")

        for j in range(len(board[0])):
            if j % 3 == 0 and j != 0:
                print(" | ", end="")

            if j == 8:
                print(board[i][j])
            else:
                print(str(board[i][j]) + " ", end="")

def find_empty(board):
    """
    Finds an empty spot (represented by 0) on the board.
    Returns (row, col) of the empty spot, or None if no empty spots.
    """
    for r in range(len(board)):
        for c in range(len(board[0])):
            if board[r][c] == 0:
                return (r, c)
    return None

def is_valid(board, num, pos):
    """
    Checks if placing 'num' at 'pos' (row, col) is valid according to Sudoku rules.
    """
    row, col = pos

    # Check Row
    for c in range(len(board[0])):
        if board[row][c] == num and col != c:
            return False

    # Check Column
    for r in range(len(board)):
        if board[r][col] == num and row != r:
            return False

    # Check 3x3 Box
    box_start_r = (row // 3) * 3
    box_start_c = (col // 3) * 3

    for r in range(box_start_r, box_start_r + 3):
        for c in range(box_start_c, box_start_c + 3):
            if board[r][c] == num and (r, c) != pos:
                return False

    return True

def solve(board):
    """
    Solves the Sudoku board using the backtracking algorithm.
    Modifies the board in place.
    Returns True if a solution is found, False otherwise.
    """
    find = find_empty(board)
    if not find:
        return True
    else:
        row, col = find

    for num in range(1, 10):
        if is_valid(board, num, (row, col)):
            board[row][col] = num

            if solve(board):
                return True

            board[row][col] = 0

    return False

print("Initial Sudoku Board:")
print_board(board)
print("\n" + "="*25 + "\n")

if solve(board):
    print("Solved Sudoku Board:")
    print_board(board)
else:
    print("No solution exists for this Sudoku board.")

How to Run Your Sudoku Solver

  1. Save the code: Copy the entire code block above into a text editor (like Notepad, VS Code, or Sublime Text). Save it as a Python file (e.g., sudoku_solver.py).
  2. Open your terminal/command prompt: Navigate to the directory where you saved the file.
  3. Run the script: Type python sudoku_solver.py and press Enter.

You should see the initial Sudoku board printed, followed by the solved board!

What’s Next?

You’ve just built a complete Sudoku solver! That’s a huge achievement. Here are some ideas for further exploration:

  • GUI: Can you create a graphical user interface (GUI) for your solver using libraries like Tkinter or Pygame?
  • Difficulty: How would you determine the difficulty of a Sudoku puzzle?
  • Puzzle Generator: Can you modify the code to generate new Sudoku puzzles? (This is a more advanced challenge!)
  • Performance: For very difficult puzzles, this solver might take a moment. Can you think of ways to make it faster?

This project is a fantastic introduction to algorithms like backtracking and recursion, which are fundamental concepts in computer science. Keep experimenting, and happy coding!

Comments

Leave a Reply