pythontkinterbacktrackingsudoku

Sudoku generator and solver in python


I am making a sudoku generator and solver project in Python for a school project and I ran into a couple of errors. The code itself does not have any mistakes, but the implementation is incorrect, so I'm stuck.

Here is the code:

from tkinter import *
import random

class SudokuBoard:
    def __init__(self, master):
        self.master = master
        self.master.title("Sudoku Board")
        self.create_board()
        self.create_buttons()
        self.board = [[0 for _ in range(9)] for _ in range(9)]

    def create_board(self):
        self.cells = {}
        for i in range(9):
            for j in range(9):
                if (i in (0, 1, 2, 6, 7, 8) and j in (3, 4, 5)) or (i in (3, 4, 5) and j in (0, 1, 2, 6, 7, 8)):
                    color = "#d9d9d9"
                else:
                    color = "white"
                self.cells[(i, j)] = Label(self.master, width=4, height=2, font=("Helvetica", 20), bg=color, relief="raised")
                self.cells[(i, j)].grid(row=i, column=j)

    def create_buttons(self):
        self.generate_button = Button(self.master, text="Generate", font=("Helvetica", 16), command=self.generate_board)
        self.generate_button.grid(row=10, column=1, columnspan=3, padx=5, pady=5)

        self.solve_button = Button(self.master, text="Solve", font=("Helvetica", 16), command=self.solve_board)
        self.solve_button.grid(row=10, column=3, columnspan=3, padx=5, pady=5)

        self.reset_button = Button(self.master, text="Reset", font=("Helvetica", 16), command=self.reset_board)
        self.reset_button.grid(row=10, column=6, columnspan=3, padx=5, pady=5)

    def generate_board(self):
        # Generate a solvable Sudoku board
        while not self.solve_board():
            pass
        self.remove_numbers()  # Remove some numbers to create a puzzle
        self.update_cells()

    def reset_board(self):
        # Reset the board to all zeros
        self.board = [[0 for _ in range(9)] for _ in range(9)]

    cells_to_remove = 50  # class-level variable

    def remove_numbers(self):
        # Remove numbers from the board to create a puzzle
        cells_removed = 0
        while cells_removed < self.cells_to_remove:
            row = random.randint(0, 8)
            col = random.randint(0, 8)
            if self.board[row][col] != 0:
                self.board[row][col] = 0
                cells_removed += 1


    def solve_board(self):
        # Solve the Sudoku board using backtracking
        def find_empty():
            for i in range(9):
                for j in range(9):
                    if self.board[i][j] == 0:
                        return (i, j)
            return None

        def is_valid(num, pos):
            # Check if num is valid in the given position
            # Check row
            for i in range(9):
                if self.board[pos[0]][i] == num and pos[1] != i:
                    return False
            # Check column
            for i in range(9):
                if self.board[i][pos[1]] == num and pos[0] != i:
                    return False
            # Check 3x3 square
            box_x = pos[1] // 3
            box_y = pos[0] // 3
            for i in range(box_y * 3, box_y * 3 + 3):
                for j in range(box_x * 3, box_x * 3 + 3):
                    if self.board[i][j] == num and (i, j) != pos:
                        return False
            return True

        def solve():
            empty = find_empty()
            if not empty:
                return True
            else:
                row, col = empty
                for num in range(1, 10):
                    if is_valid(num, (row, col)):
                        self.board[row][col] = num
                        if solve():
                            return True
                        self.board[row][col] = 0
                return False

        return solve()

    def update_cells(self):
        # Update the cells with the current board values
        for i in range(9):
            for j in range(9):
                if self.board[i][j] != 0:
                    self.cells[(i, j)].config(text=self.board[i][j], fg="black")
                else:
                    self.cells[(i, j)].config(text="", fg="black")

root = Tk()
sudoku_board = SudokuBoard(root)
root.mainloop()

I feel like there is a problem with the "remove numbers" function, the "solve board" function and the solve function but I can't identify the problem so I can fix it.

You have any ideas?


Solution

  • You forgot to "refresh" the cells (by calling update_cells) after calling solve_board or reset_board, so the GUI was not updated. Fix:

    from tkinter import *
    import random
    
    class SudokuBoard:
        def __init__(self, master):
            self.master = master
            self.master.title("Sudoku Board")
            self.create_board()
            self.create_buttons()
            self.board = [[0 for _ in range(9)] for _ in range(9)]
    
        def create_board(self):
            self.cells = {}
            for i in range(9):
                for j in range(9):
                    if (i in (0, 1, 2, 6, 7, 8) and j in (3, 4, 5)) or (i in (3, 4, 5) and j in (0, 1, 2, 6, 7, 8)):
                        color = "#d9d9d9"
                    else:
                        color = "white"
                    self.cells[(i, j)] = Label(self.master, width=4, height=2, font=("Helvetica", 20), bg=color, relief="raised")
                    self.cells[(i, j)].grid(row=i, column=j)
    
        def create_buttons(self):
            self.generate_button = Button(self.master, text="Generate", font=("Helvetica", 16), command=self.generate_board)
            self.generate_button.grid(row=10, column=1, columnspan=3, padx=5, pady=5)
    
            self.solve_button = Button(self.master, text="Solve", font=("Helvetica", 16), command=self.solve_board)
            self.solve_button.grid(row=10, column=3, columnspan=3, padx=5, pady=5)
    
            self.reset_button = Button(self.master, text="Reset", font=("Helvetica", 16), command=self.reset_board)
            self.reset_button.grid(row=10, column=6, columnspan=3, padx=5, pady=5)
    
        def generate_board(self):
            # Generate a solvable Sudoku board
            while not self.solve_board():
                pass
            self.remove_numbers()  # Remove some numbers to create a puzzle
            self.update_cells()
    
        def reset_board(self):
            # Reset the board to all zeros
            self.board = [[0 for _ in range(9)] for _ in range(9)]
            self.update_cells()
    
        cells_to_remove = 50  # class-level variable
    
        def remove_numbers(self):
            # Remove numbers from the board to create a puzzle
            cells_removed = 0
            while cells_removed < self.cells_to_remove:
                row = random.randint(0, 8)
                col = random.randint(0, 8)
                if self.board[row][col] != 0:
                    self.board[row][col] = 0
                    cells_removed += 1
    
    
        def solve_board(self):
            # Solve the Sudoku board using backtracking
            def find_empty():
                for i in range(9):
                    for j in range(9):
                        if self.board[i][j] == 0:
                            return (i, j)
                return None
    
            def is_valid(num, pos):
                # Check if num is valid in the given position
                # Check row
                for i in range(9):
                    if self.board[pos[0]][i] == num and pos[1] != i:
                        return False
                # Check column
                for i in range(9):
                    if self.board[i][pos[1]] == num and pos[0] != i:
                        return False
                # Check 3x3 square
                box_x = pos[1] // 3
                box_y = pos[0] // 3
                for i in range(box_y * 3, box_y * 3 + 3):
                    for j in range(box_x * 3, box_x * 3 + 3):
                        if self.board[i][j] == num and (i, j) != pos:
                            return False
                return True
    
            def solve():
                empty = find_empty()
                if not empty:
                    return True
                else:
                    row, col = empty
                    for num in range(1, 10):
                        if is_valid(num, (row, col)):
                            self.board[row][col] = num
                            if solve():
                                return True
                            self.board[row][col] = 0
                    return False
    
            value = solve()
            self.update_cells()
            return value
    
        def update_cells(self):
            # Update the cells with the current board values
            for i in range(9):
                for j in range(9):
                    if self.board[i][j] != 0:
                        self.cells[(i, j)].config(text=self.board[i][j], fg="black")
                    else:
                        self.cells[(i, j)].config(text="", fg="black")
    
    root = Tk()
    sudoku_board = SudokuBoard(root)
    root.mainloop()