Okay, I have split my code into 4 files:
1. main function:
I am very open to improvement suggestions.
1. main function:
# SudokuSolver_main.py import SudokuSolver_class as Sudokuolver from tkinter import * def main(): root = Tk() app = Sudokuolver.SudokuSolver(root) root.mainloop() if __name__ == '__main__': main()2. Class with Userinterface
# SudokuSolver_class.py import os import SudokuSolver_solver as Solver from random import randint from tkinter import * from tkcallasync import tk_call_async, MULTIPROCESSING class SudokuSolver(Frame): disabled = False backtrack_ctr = 0 sudoku = [[" " for col in range(9)] for row in range(9)] solution = [[" " for col in range(9)] for row in range(9)] exampleSudoku = [[' ',' ',' ','2','1',' ',' ',' ',' '], [' ',' ','7','3',' ',' ',' ',' ',' '], [' ','5','8',' ',' ',' ',' ',' ',' '], ['4','3',' ',' ',' ',' ',' ',' ',' '], ['2',' ',' ',' ',' ',' ',' ',' ','8'], [' ',' ',' ',' ',' ',' ',' ','7','6'], [' ',' ',' ',' ',' ',' ','2','5',' '], [' ',' ',' ',' ',' ','7','3',' ',' '], [' ',' ',' ',' ','9','8',' ',' ',' ']] def __init__(self, parent): Frame.__init__(self, parent, name='frame') self.parent = parent self.initUI() self.blankSUDOKU() def initUI(self): self.message_txt = StringVar() self.values = {} self.parent.title('Sudoku Solver') self.pack(fill=BOTH, expand=True) self.message = Label(self, textvariable=self.message_txt) self.message.grid(row=10,column=0,columnspan=8, sticky=E+W) self.solveBtn = Button(self, text="solve",width=6,command=self.execute_solver) self.solveBtn.grid(row=0,column=0) self.newBtn = Button(self, text="new",width=6,command=self.blankSUDOKU) self.newBtn.grid(row=0,column=1) self.initBtn = Button(self, text="init",width=6,command=self.initSUDOKU) self.initBtn.grid(row=0,column=2) self.printBtn = Button(self, text="print",width=6,command=self.printSUDOKU) self.printBtn.grid(row=0,column=8) self.checkBtn = Button(self, text="check",width=6,command=self.checkSOLUTION) self.checkBtn.grid(row=0,column=6) self.saveBtn = Button(self, text="save",width=6,command=self.saveSUDOKU) self.saveBtn.grid(row=0,column=7) # --- clear all fields and delete all values in sudoku[][] and solution[][] --- # def blankSUDOKU(self): self.message_txt.set('') for row in range(9): for col in range(9): self.sudoku[row][col] = ' ' self.solution[row][col] = ' ' self.numEntry = Entry(self, width=5, justify=CENTER, font="Helvetica 12 bold") self.values[row,col] = self.numEntry self.numEntry.grid(row=row+1,column=col, ipady=5) # ----- get a random Sudoku from textfiles ----- # def initSUDOKU(self): self.blankSUDOKU() self.message_txt.set('') L = len(os.listdir(os.getcwd() + "\sudokus")) / 2 if L < 1: self.message_txt.set('No Sudokus saved, yet! Try this one') self.sudoku = self.copySUDOKU(self.exampleSudoku) else: # ----- fill sudoku array with values from textfile ----- # sudokuFile = open(os.getcwd() + "\sudokus\Sudoku%s.txt" % (randint(1, L))) row, col = 0, 0 for value in sudokuFile.read(): if value.isdigit() or value==' ': self.sudoku[row][col] = value col += 1 if col==9: if row==8: break row += 1 col = 0 sudokuFile.close() # ----- make values from random sudoku appear in the GUI ----- # for row in range(9): for col in range(9): value = self.sudoku[row][col] if value!=' ': self.values[row,col].insert(0,value) def printSUDOKU(self, matrix): for row in range(9): print("|-----------------------------------|") for col in range(9): print("| " + matrix[row][col] + " ",end='') print("|") print("|-----------------------------------|") def saveSUDOKU(self): CHECK = self.checkSUDOKU(self.solution) if CHECK[0] and CHECK[1]: # check if Sudoku is valid and complete filecount = 1 # variable for how many sudoku files are already existing while os.path.isfile(os.getcwd() + "\sudokus\sudoku%s.txt" % filecount): filecount += 1 sudokuFile = open(os.getcwd() + "\sudokus\sudoku%s.txt" % filecount, 'w') solutionFile = open(os.getcwd() + "\sudokus\solution%s.txt" % filecount, 'w') # ----- create textfiles for sudoku and the solution ----- # for row in range(9): for col in range(9): sudokuFile.write(self.sudoku[row][col]) solutionFile.write(self.solution[row][col]) sudokuFile.write('\n') solutionFile.write('\n') sudokuFile.close() solutionFile.close() self.message_txt.set('Files saved...') else: self.message_txt.set('Sudoku must be valid and complete!') def copySUDOKU(self, matrix): matrix_copy = [[" " for col in range(9)] for row in range(9)] for row in range(9): for col in range(9): matrix_copy[row][col] = matrix[row][col] return matrix_copy # ----- check if user input makes sense -----# def checkSUDOKU(self, matrix): valid, complete, ctr = True, True, 0 for row in range(9): for col in range(9): value = matrix[row][col] if value!=' ': matrix[row][col]=' ' if not self.consistent(matrix, row, col, value): valid, complete = False, True return valid, complete, row, col matrix[row][col] = value ctr += 1 else: complete = False if ctr<17: valid = False return valid, complete # ----- check if solution is valid ----- # def checkSOLUTION(self): self.getinput(self.sudoku) CHECK = self.checkSUDOKU(self.sudoku) if CHECK[0] and CHECK[1]: self.message_txt.set("Solution correct!") elif not CHECK[1]: self.message_txt.set("Sudoku is not complete!") elif not CHECK[0]: self.message_txt.set("Mistake at row %s, col %s" % (CHECK[2], CHECK[3])) # ----- write user input into values{} ----- # def getinput(self, matrix): for row in range(9): for col in range(9): value = self.values[row,col].get() if value=='': matrix[row][col] = ' ' elif len(value)!=1: matrix[row][col] = ' ' elif not value.isdigit(): matrix[row][col] = ' ' elif int(value)<1 or int(value)>9: matrix[row][col] = ' ' else : matrix[row][col] = value # ----- check if this input leads to a valid solution ----- # def consistent(self, matrix, row, col, value): for i in range(9): if matrix[row][i]==value: return False if matrix[i][col]==value: return False rowStart = row - row%3 colStart = col - col%3 for m in range(3): for k in range(3): if matrix[rowStart+k][colStart+m]==value: return False return True def execute_solver(self): self.getinput(self.sudoku) # user input from GUI -> sudoku array if not self.checkSUDOKU(self.sudoku)[0]: # check if user input makes sense self.message_txt.set("This sudoku does not have a solution") else: if self.disabled: self.message_txt.set("warning, It's still calculating...") return def callback(result): self.disabled self.disabled = False status, self.solution = result self.printSUDOKU(self.sudoku) self.printSUDOKU(self.solution) self.message_txt.set('Solution found!' if status else 'No Solution found!') self.fill_solution() self.disabled = True tk_call_async(self, Solver.do_solve, args=(self.sudoku,), callback=callback, method=MULTIPROCESSING) def fill_solution(self): for row in range(9): for col in range(9): self.values[row,col].delete(0,END) self.values[row,col].insert(0,self.solution[row][col])3. the actual solving algorithm
# SudokuSolver_solver.py def do_solve(sudoku): status = wsolve(sudoku) return status, sudoku def consistent(matrix, row, col, value): for i in range(9): if matrix[row][i]==value: return False if matrix[i][col]==value: return False rowStart = row - row%3 colStart = col - col%3 for m in range(3): for k in range(3): if matrix[rowStart+k][colStart+m]==value: return False return True class SolutionFound(Exception): pass def wsolve(sudoku): try: solve(sudoku, 0) except SolutionFound: return True return False def solve(sudoku, num): if num==81: raise SolutionFound return True else: row = int(num / 9) col = num % 9 if sudoku[row][col]!=' ': solve(sudoku, num+1) else: for value in range(1,10): if consistent(sudoku, row, col, str(value)): sudoku[row][col] = str(value) if solve(sudoku, num+1): return True sudoku[row][col]=' ' return False if __name__ == '__main__': printSUDOKU(sudoku) solve(sudoku, 0)4. tkcallasync.py - nothing changed on that code
I am very open to improvement suggestions.