Apr-10-2018, 09:03 AM
Hey, I have recently switched from Tkinter to wxPython, because after reading several comments on that topic I came to the conclusion that this might be a good idea.
In order to keep the GUI responsive during backtracking, I used threading like in this example: Long running tasks
I also succeeded to implement a working "Stop Button" to abort the backtracking thread.
If someone is interested, here is the full code:
I am very thankful for any improvement suggestions on the code or the "project" in general. I am still new to programming and I want to learn how to get better.
In order to keep the GUI responsive during backtracking, I used threading like in this example: Long running tasks
I also succeeded to implement a working "Stop Button" to abort the backtracking thread.
If someone is interested, here is the full code:
# GUI File import wx import time import SudokuSolverV2_Solver as SOLVER from threading import * START_ID = wx.NewId() STOP_ID = wx.NewId() EVT_RESULT_ID = wx.NewId() class ResultEvent(wx.PyEvent): def __init__(self, data): wx.PyEvent.__init__(self) self.SetEventType(EVT_RESULT_ID) self.data = data class WorkerThread(Thread): def __init__(self, notify_window, *args): Thread.__init__(self) self._notify_window = notify_window self.matrix = args[0] self.aborted = False self.start() def run(self): if self.aborted: wx.PostEvent(self._notify_window, ResultEvent(None)) return SOLVER.solve(self.matrix, 0) wx.PostEvent(self._notify_window, ResultEvent(SOLVER.SOLUTION)) def abort(self): self.aborted = True SOLVER.DISABLED = True self.run() class SudokuSolver(wx.Frame): def __init__(self, parent): super(SudokuSolver, self).__init__(parent, title='SudokuSolver' ,size=(355,435), style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX) self.Entries = {} self.seconds = 0 self.minutes = 0 self.timer_mode = 'stopped' self.Connect(-1, -1, EVT_RESULT_ID, self.OnResult) self.worker = None self.InitUI() self.Centre() self.Show() def InitUI(self): menuBar = wx.MenuBar() FileBtn = wx.Menu() InitItem = FileBtn.Append(wx.NewId(), 'Init') NewItem = FileBtn.Append(wx.NewId(), 'New') SaveItem = FileBtn.Append(wx.NewId(), 'Save') ExitItem = FileBtn.Append(wx.ID_EXIT, 'Exit') self.Bind(wx.EVT_MENU, self.InitSudoku, InitItem) self.Bind(wx.EVT_MENU, self.BlankGrid, NewItem) self.Bind(wx.EVT_MENU, self.SaveSudoku, SaveItem) self.Bind(wx.EVT_MENU, self.Quit, ExitItem) menuBar.Append(FileBtn, '&File') OptionsBtn = wx.Menu() TimerStartItem = OptionsBtn.Append(wx.NewId(), 'Start Timer') TimerStopItem = OptionsBtn.Append(wx.NewId(), 'Stop Timer') self.Bind(wx.EVT_MENU, self.StartTimer, TimerStartItem) self.Bind(wx.EVT_MENU, self.StopTimer, TimerStopItem) menuBar.Append(OptionsBtn, '&Options') HelpBtn = wx.Menu() menuBar.Append(HelpBtn, '&Help') self.SetMenuBar(menuBar) self.panel = wx.Panel(self) self.font = wx.Font(wx.FontInfo(15).Bold()) self.InitGrid() self.message_label = wx.StaticText(self.panel, label='', pos=(10,358)) self.timer_label = wx.StaticText(self.panel, label='00:00', pos=(280,358)) #self.PrintBtn = wx.Button(self.panel, pos=(0,0), size=(50,28), label='Print') #self.PrintBtn.Bind(wx.EVT_BUTTON, self.PrintSudokus) self.StartBtn = wx.Button(self.panel, START_ID, label='Solve', size=(50,28), pos=(88,0)) self.StartBtn.Bind(wx.EVT_BUTTON, self.OnStart, id=START_ID) self.CheckBtn = wx.Button(self.panel, pos=(146,0), size=(50,28), label='Check') self.CheckBtn.Bind(wx.EVT_BUTTON, self.CheckSudoku) #self.TimeBtn = wx.Button(self.panel, pos=(202,0), size=(50,28), label='Start') #self.TimeBtn.Bind(wx.EVT_BUTTON, self.StartTimer) self.StopBtn = wx.Button(self.panel, STOP_ID, label='Stop', size=(50,28), pos=(202,0)) self.StopBtn.Bind(wx.EVT_BUTTON, self.OnStop, id=STOP_ID) def Quit(self, event): self.Close() def InitGrid(self): x, y = 10, 30 for i in range(1,10): for j in range(1,10): self.Entries[i,j] = wx.TextCtrl(self.panel, style=wx.TE_CENTRE, pos=(x, y), size=(30, 30)) self.Entries[i,j].SetFont(self.font) if j%3==0: x += 40 else: x += 35 if j==9: x = 10 if i%3==0: y += 40 else: y += 35 def BlankGrid(self, event, del_matrix=True): self.message_label.SetLabel('') self.ResetTimer() if del_matrix: SOLVER.clear() for i in range(1,10): for j in range(1,10): L = self.Entries[i,j].GetLineLength(0) self.Entries[i,j].Remove(0,L) def InitSudoku(self, event): self.BlankGrid(None) SOLVER.SUDOKU = SOLVER.sudoku_from_txt() for i in range(1,10): for j in range(1,10): self.Entries[i,j].write(SOLVER.SUDOKU.matrix[i-1][j-1]) SOLVER.SUDOKU.draw() def SaveSudoku(self, event): if SOLVER.SOLUTION.valid() and SOLVER.SOLUTION.complete(): SOLVER.sudoku_to_txt() self.message_label.SetLabel('Files saved...') else: self.message_label.SetLabel('Sudoku must be valid and complete!') def CheckSudoku(self, event): self.GetEntries(SOLVER.SOLUTION) if not SOLVER.SOLUTION.complete(): self.message_label.SetLabel('Sudoku is not complete!') elif not SOLVER.SOLUTION.valid(): self.message_label.SetLabel('Solution is not correct!') else: self.message_label.SetLabel('Solution correct :)') def GetEntries(self, Sudoku): for i in range(1,10): for j in range(1,10): val = self.Entries[i,j].GetLineText(0) if val=='': Sudoku.matrix[i-1][j-1] = ' ' elif len(val)!=1: Sudoku.matrix[i-1][j-1] = ' ' elif not val.isdigit(): Sudoku.matrix[i-1][j-1] = ' ' elif int(val) < 1 or int(val) > 9: Sudoku.matrix[i-1][j-1] = ' ' else : Sudoku.matrix[i-1][j-1] = val def ShowSolution(self, data): self.BlankGrid(None, False) for i in range(1,10): for j in range(1,10): self.Entries[i,j].write(data.matrix[i-1][j-1]) def StartTimer(self, event): self.timer_mode = 'started' def timer(): if self.timer_mode == 'stopped': return if self.seconds == 60: self.seconds = 0 self.minutes += 1 if self.seconds > 9 and self.minutes > 9: self.timer_label.SetLabel('{}:{}'.format(self.minutes, self.seconds)) elif self.seconds > 9 and self.minutes <= 9: self.timer_label.SetLabel('0{}:{}'.format(self.minutes, self.seconds)) elif self.seconds <= 9 and self.minutes <= 9: self.timer_label.SetLabel('0{}:0{}'.format(self.minutes, self.seconds)) elif self.seconds <= 9 and self.minutes > 9: self.timer_label.SetLabel('{}:0{}'.format(self.minutes, self.seconds)) self.seconds += 1 wx.CallLater(1000, timer) timer() def StopTimer(self, event): self.timer_mode = 'stopped' def ResetTimer(self): self.minutes = 0 self.seconds = 0 self.timer_mode = 'stopped' self.timer_label.SetLabel('0{}:0{}'.format(self.minutes, self.seconds)) def OnStart(self, event): clues, valid = SOLVER.SUDOKU.clues(), SOLVER.SUDOKU.valid() self.message_label.SetLabel('clues: {}, valid: {}'.format(clues, valid)) if not clues >= 17 or not valid: self.message_label.SetLabel('This sudoku does not have a solution') else: matrix = SOLVER.SUDOKU.copy(SOLVER.SUDOKU.matrix) if not self.worker: self.message_label.SetLabel('Backtracking started...') self.worker = WorkerThread(self, matrix) else: self.message_label.SetLabel('WARNING: Still backtracking...') def OnStop(self, event): if self.worker: SOLVER.DISABLED = True self.message_label.SetLabel('backtracking aborted') self.worker.abort() else: self.message_label.SetLabel('currently not backtracking') def OnResult(self, event): if event.data is None: self.ShowSolution(SOLVER.SUDOKU) self.message_label.SetLabel('Computation aborted') else: self.ShowSolution(event.data) self.message_label.SetLabel('Solution found! {} backtracks needed'.format(SOLVER.BACKTRACKS)) self.worker = None SOLVER.BACKTRACKS = 0 SOLVER.DISABLED = 0 """ def PrintSudokus(self, event): print('Worker: ', self.worker) print('Sudoku:') SOLVER.SUDOKU.draw() print('Solution:') SOLVER.SOLUTION.draw() """ if __name__ == '__main__': app = wx.App() SudokuSolver(None) app.MainLoop()
# Solver File import SudokuSolverV2_GUI as SudokuSolver import SudokuSolverV2_SudokuClass as SUDOKU_ import os from random import randint def sudoku_from_txt(): sudoku_dir = os.path.join(os.getcwd(), 'sudokus') if not os.path.exists(sudoku_dir): Sudoku = SUDOKU_.Sudoku(EXAMPLE) return Sudoku Sudoku = SUDOKU_.Sudoku() Files = [] for filename in os.listdir(sudoku_dir): if filename.startswith('sudoku') and filename.endswith('.txt'): Files.append(filename) if len(Files) < 1: print('ERROR: No Sudokus saved, yet! Try this one') return Sudoku(example) else: R = randint(0, len(Files)-1) sudokuFile = open(os.path.join(sudoku_dir,Files[R])) row, col = 0, 0 for value in sudokuFile.read(): if value.isdigit() or value==' ': if value=='0': value = ' ' # zeros can also be used as blanks Sudoku.matrix[row][col] = value col += 1 if col==9: if row==8: break row += 1 col = 0 sudokuFile.close() if not Sudoku.valid(): print('{} does not contain a valid sudoku'.format(Files[R])) return SUDOKU.Sudoku() else: return Sudoku def sudoku_to_txt(): sudoku_dir = os.path.join(os.getcwd(), 'sudokus') if not os.path.exists(sudoku_dir): os.makedirs(sudoku_dir) filecount = 1 # variable for how many sudoku files are already existing while os.path.isfile(os.path.join(sudoku_dir,("sudoku%s.txt" % filecount))): filecount += 1 # ----- create textfiles for sudoku and the solution ----- # sudokuFile = open(os.path.join(sudoku_dir,('sudoku%s.txt' % filecount)), 'w') solutionFile = open(os.path.join(sudoku_dir,('solution%s.txt' % filecount)), 'w') for row in range(9): for col in range(9): sudokuFile.write(SUDOKU.matrix[row][col]) solutionFile.write(SOLUTION.matrix[row][col]) sudokuFile.write('\n') solutionFile.write('\n') sudokuFile.close() solutionFile.close() 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 def solve(matrix, num): global BACKTRACKS, DISABLED if DISABLED: return True BACKTRACKS += 1 if num==81: global SOLUTION SOLUTION = SUDOKU_.Sudoku(matrix) return True else: row = int(num / 9) col = num % 9 if matrix[row][col]!=' ': solve(matrix, num+1) else: for value in range(1,10): if consistent(matrix, row, col, str(value)): matrix[row][col] = str(value) if solve(matrix, num+1): return True matrix[row][col]=' ' return False def clear(): SUDOKU.matrix = SUDOKU.emptySudoku() SOLUTION.matrix = SOLUTION.emptySudoku() DISABLED = False BACKTRACKS = 0 SUDOKU = SUDOKU_.Sudoku() SOLUTION = SUDOKU_.Sudoku() EXAMPLE = [[' ',' ',' ','2','1',' ',' ',' ',' '], [' ',' ','7','3',' ',' ',' ',' ',' '], [' ','5','8',' ',' ',' ',' ',' ',' '], ['4','3',' ',' ',' ',' ',' ',' ',' '], ['2',' ',' ',' ',' ',' ',' ',' ','8'], [' ',' ',' ',' ',' ',' ',' ','7','6'], [' ',' ',' ',' ',' ',' ','2','5',' '], [' ',' ',' ',' ',' ','7','3',' ',' '], [' ',' ',' ',' ','9','8',' ',' ',' ']] if __name__ == '__main__': Sudoku = sudoku_from_txt() Sudoku.draw()
# SudokuClass File class Sudoku(): def __init__(self, *args): if len(args) > 0: self.matrix = self.copy(args[0]) else: self.matrix = self.emptySudoku() def draw(self): for row in range(9): print("|-----------------------------------|") for col in range(9): print("| " + self.matrix[row][col] + " ",end='') print("|") print("|-----------------------------------|") def get(self, row, col): return self.matrix[row][col] def copy(self, matrix): matrix_copy = self.emptySudoku() for row in range(9): for col in range(9): matrix_copy[row][col] = matrix[row][col] return matrix_copy def emptySudoku(self): return [[" " for col in range(9)] for row in range(9)] def complete(self): for row in range(9): for col in range(9): if self.matrix[row][col] == ' ': return False return True def valid(self): for row in range(9): for col in range(9): val = self.matrix[row][col] if val != ' ': self.matrix[row][col] = ' ' if not self.consistent(row, col, val): self.matrix[row][col] = val return False self.matrix[row][col] = val return True def clues(self): clues = 0 for row in range(9): for col in range(9): if self.matrix[row][col] != ' ': clues += 1 return clues def consistent(self, row, col, value): for i in range(9): if self.matrix[row][i]==value: return False if self.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 self.matrix[rowStart+k][colStart+m]==value: return False return True if __name__ == '__main__': matrix = [[' ',' ',' ','2','1',' ',' ',' ',' '], [' ',' ','7','3',' ',' ',' ',' ',' '], [' ','5','8',' ',' ',' ',' ',' ',' '], ['4','3',' ',' ',' ',' ',' ',' ',' '], ['2',' ',' ',' ',' ',' ',' ',' ','8'], [' ',' ',' ',' ',' ',' ',' ','7','6'], [' ',' ',' ',' ',' ',' ','2','5',' '], [' ',' ',' ',' ',' ','7','3',' ',' '], [' ',' ',' ',' ','9','8',' ',' ',' ']] S = Sudoku(matrix) S.draw() print('(1,4) = {}, valid = {}, clues = {}, complete = {}'.format(S.get(0,3), S.valid(), S.clues(), S.complete()))The "project" is still far from being finished. I am considering to add more options and some kind of README file.
I am very thankful for any improvement suggestions on the code or the "project" in general. I am still new to programming and I want to learn how to get better.