├── requirements.txt ├── .gitpod.yml ├── README.md ├── solver.py ├── solver (text).py └── GUI.py /requirements.txt: -------------------------------------------------------------------------------- 1 | pygame 2 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-full-vnc 2 | ports: 3 | - port: 5900 4 | onOpen: ignore 5 | - port: 6080 6 | onOpen: open-preview 7 | tasks: 8 | - init: pip3 install -r requirements.txt 9 | command: python3 GUI.py 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sudoku-GUI-Solver 2 | This is a sudoku solver using the backtracking algorithm. It includes a graphical GUI as well as a text based version. 3 | 4 | Run GUI.py to play sudoku. 5 | 6 | # Instructions 7 | Click a box and hit the number on your keybaord to pencil in a number. To confirm that value press the ENTER key on that box. To delete a pencil in you can click DEL. Finally to solve the board press SPACE, sit back and watch the algorithm run. 8 | 9 | # Video Tutorial 10 | 11 | You can view the video tutorials on how to create this project here: https://www.youtube.com/watch?v=eqUwSA0xI-s&t=871s 12 | 13 | # 💻 Launch Your Software Development Career Today! 14 | 15 | 🎓 **No degree? No problem!** My program equips you with everything you need to break into tech and land an entry-level software development role. 16 | 17 | 🚀 **Why Join?** 18 | - 💼 **$70k+ starting salary potential** 19 | - 🕐 **Self-paced:** Complete on your own time 20 | - 🤑 **Affordable:** Low risk compared to expensive bootcamps or degrees 21 | - 🎯 **45,000+ job openings** in the market 22 | 23 | 👉 **[Start your journey today!](https://techwithtim.net/dev)** 24 | No experience needed—just your determination. Future-proof your career and unlock six-figure potential like many of our students have! 25 | -------------------------------------------------------------------------------- /solver.py: -------------------------------------------------------------------------------- 1 | # solver.py 2 | 3 | def solve(bo): 4 | find = find_empty(bo) 5 | if not find: 6 | return True 7 | else: 8 | row, col = find 9 | 10 | for i in range(1,10): 11 | if valid(bo, i, (row, col)): 12 | bo[row][col] = i 13 | 14 | if solve(bo): 15 | return True 16 | 17 | bo[row][col] = 0 18 | 19 | return False 20 | 21 | 22 | def valid(bo, num, pos): 23 | # Check row 24 | for i in range(len(bo[0])): 25 | if bo[pos[0]][i] == num and pos[1] != i: 26 | return False 27 | 28 | # Check column 29 | for i in range(len(bo)): 30 | if bo[i][pos[1]] == num and pos[0] != i: 31 | return False 32 | 33 | # Check box 34 | box_x = pos[1] // 3 35 | box_y = pos[0] // 3 36 | 37 | for i in range(box_y*3, box_y*3 + 3): 38 | for j in range(box_x * 3, box_x*3 + 3): 39 | if bo[i][j] == num and (i,j) != pos: 40 | return False 41 | 42 | return True 43 | 44 | 45 | def print_board(bo): 46 | for i in range(len(bo)): 47 | if i % 3 == 0 and i != 0: 48 | print("- - - - - - - - - - - - - ") 49 | 50 | for j in range(len(bo[0])): 51 | if j % 3 == 0 and j != 0: 52 | print(" | ", end="") 53 | 54 | if j == 8: 55 | print(bo[i][j]) 56 | else: 57 | print(str(bo[i][j]) + " ", end="") 58 | 59 | 60 | def find_empty(bo): 61 | for i in range(len(bo)): 62 | for j in range(len(bo[0])): 63 | if bo[i][j] == 0: 64 | return (i, j) # row, col 65 | 66 | return None 67 | -------------------------------------------------------------------------------- /solver (text).py: -------------------------------------------------------------------------------- 1 | # solver.py 2 | def solve(bo): 3 | """ 4 | Solves a sudoku board using backtracking 5 | :param bo: 2d list of ints 6 | :return: solution 7 | """ 8 | find = find_empty(bo) 9 | if find: 10 | row, col = find 11 | else: 12 | return True 13 | 14 | for i in range(1,10): 15 | if valid(bo, (row, col), i): 16 | bo[row][col] = i 17 | 18 | if solve(bo): 19 | return True 20 | 21 | bo[row][col] = 0 22 | 23 | return False 24 | 25 | 26 | def valid(bo, pos, num): 27 | """ 28 | Returns if the attempted move is valid 29 | :param bo: 2d list of ints 30 | :param pos: (row, col) 31 | :param num: int 32 | :return: bool 33 | """ 34 | 35 | # Check row 36 | for i in range(0, len(bo)): 37 | if bo[pos[0]][i] == num and pos[1] != i: 38 | return False 39 | 40 | # Check Col 41 | for i in range(0, len(bo)): 42 | if bo[i][pos[1]] == num and pos[1] != i: 43 | return False 44 | 45 | # Check box 46 | 47 | box_x = pos[1]//3 48 | box_y = pos[0]//3 49 | 50 | for i in range(box_y*3, box_y*3 + 3): 51 | for j in range(box_x*3, box_x*3 + 3): 52 | if bo[i][j] == num and (i,j) != pos: 53 | return False 54 | 55 | return True 56 | 57 | 58 | def find_empty(bo): 59 | """ 60 | finds an empty space in the board 61 | :param bo: partially complete board 62 | :return: (int, int) row col 63 | """ 64 | 65 | for i in range(len(bo)): 66 | for j in range(len(bo[0])): 67 | if bo[i][j] == 0: 68 | return (i, j) 69 | 70 | return None 71 | 72 | 73 | def print_board(bo): 74 | """ 75 | prints the board 76 | :param bo: 2d List of ints 77 | :return: None 78 | """ 79 | for i in range(len(bo)): 80 | if i % 3 == 0 and i != 0: 81 | print("- - - - - - - - - - - - - -") 82 | for j in range(len(bo[0])): 83 | if j % 3 == 0: 84 | print(" | ",end="") 85 | 86 | if j == 8: 87 | print(bo[i][j], end="\n") 88 | else: 89 | print(str(bo[i][j]) + " ", end="") 90 | 91 | -------------------------------------------------------------------------------- /GUI.py: -------------------------------------------------------------------------------- 1 | # GUI.py 2 | import pygame 3 | import time 4 | pygame.font.init() 5 | 6 | 7 | class Grid: 8 | board = [ 9 | [7, 8, 0, 4, 0, 0, 1, 2, 0], 10 | [6, 0, 0, 0, 7, 5, 0, 0, 9], 11 | [0, 0, 0, 6, 0, 1, 0, 7, 8], 12 | [0, 0, 7, 0, 4, 0, 2, 6, 0], 13 | [0, 0, 1, 0, 5, 0, 9, 3, 0], 14 | [9, 0, 4, 0, 6, 0, 0, 0, 5], 15 | [0, 7, 0, 3, 0, 0, 0, 1, 2], 16 | [1, 2, 0, 0, 0, 7, 4, 0, 0], 17 | [0, 4, 9, 2, 0, 6, 0, 0, 7] 18 | ] 19 | 20 | def __init__(self, rows, cols, width, height, win): 21 | self.rows = rows 22 | self.cols = cols 23 | self.cubes = [[Cube(self.board[i][j], i, j, width, height) for j in range(cols)] for i in range(rows)] 24 | self.width = width 25 | self.height = height 26 | self.model = None 27 | self.update_model() 28 | self.selected = None 29 | self.win = win 30 | 31 | def update_model(self): 32 | self.model = [[self.cubes[i][j].value for j in range(self.cols)] for i in range(self.rows)] 33 | 34 | def place(self, val): 35 | row, col = self.selected 36 | if self.cubes[row][col].value == 0: 37 | self.cubes[row][col].set(val) 38 | self.update_model() 39 | 40 | if valid(self.model, val, (row,col)) and self.solve(): 41 | return True 42 | else: 43 | self.cubes[row][col].set(0) 44 | self.cubes[row][col].set_temp(0) 45 | self.update_model() 46 | return False 47 | 48 | def sketch(self, val): 49 | row, col = self.selected 50 | self.cubes[row][col].set_temp(val) 51 | 52 | def draw(self): 53 | # Draw Grid Lines 54 | gap = self.width / 9 55 | for i in range(self.rows+1): 56 | if i % 3 == 0 and i != 0: 57 | thick = 4 58 | else: 59 | thick = 1 60 | pygame.draw.line(self.win, (0,0,0), (0, i*gap), (self.width, i*gap), thick) 61 | pygame.draw.line(self.win, (0, 0, 0), (i * gap, 0), (i * gap, self.height), thick) 62 | 63 | # Draw Cubes 64 | for i in range(self.rows): 65 | for j in range(self.cols): 66 | self.cubes[i][j].draw(self.win) 67 | 68 | def select(self, row, col): 69 | # Reset all other 70 | for i in range(self.rows): 71 | for j in range(self.cols): 72 | self.cubes[i][j].selected = False 73 | 74 | self.cubes[row][col].selected = True 75 | self.selected = (row, col) 76 | 77 | def clear(self): 78 | row, col = self.selected 79 | if self.cubes[row][col].value == 0: 80 | self.cubes[row][col].set_temp(0) 81 | 82 | def click(self, pos): 83 | """ 84 | :param: pos 85 | :return: (row, col) 86 | """ 87 | if pos[0] < self.width and pos[1] < self.height: 88 | gap = self.width / 9 89 | x = pos[0] // gap 90 | y = pos[1] // gap 91 | return (int(y),int(x)) 92 | else: 93 | return None 94 | 95 | def is_finished(self): 96 | for i in range(self.rows): 97 | for j in range(self.cols): 98 | if self.cubes[i][j].value == 0: 99 | return False 100 | return True 101 | 102 | def solve(self): 103 | find = find_empty(self.model) 104 | if not find: 105 | return True 106 | else: 107 | row, col = find 108 | 109 | for i in range(1, 10): 110 | if valid(self.model, i, (row, col)): 111 | self.model[row][col] = i 112 | 113 | if self.solve(): 114 | return True 115 | 116 | self.model[row][col] = 0 117 | 118 | return False 119 | 120 | def solve_gui(self): 121 | self.update_model() 122 | find = find_empty(self.model) 123 | if not find: 124 | return True 125 | else: 126 | row, col = find 127 | 128 | for i in range(1, 10): 129 | if valid(self.model, i, (row, col)): 130 | self.model[row][col] = i 131 | self.cubes[row][col].set(i) 132 | self.cubes[row][col].draw_change(self.win, True) 133 | self.update_model() 134 | pygame.display.update() 135 | pygame.time.delay(100) 136 | 137 | if self.solve_gui(): 138 | return True 139 | 140 | self.model[row][col] = 0 141 | self.cubes[row][col].set(0) 142 | self.update_model() 143 | self.cubes[row][col].draw_change(self.win, False) 144 | pygame.display.update() 145 | pygame.time.delay(100) 146 | 147 | return False 148 | 149 | 150 | class Cube: 151 | rows = 9 152 | cols = 9 153 | 154 | def __init__(self, value, row, col, width, height): 155 | self.value = value 156 | self.temp = 0 157 | self.row = row 158 | self.col = col 159 | self.width = width 160 | self.height = height 161 | self.selected = False 162 | 163 | def draw(self, win): 164 | fnt = pygame.font.SysFont("comicsans", 40) 165 | 166 | gap = self.width / 9 167 | x = self.col * gap 168 | y = self.row * gap 169 | 170 | if self.temp != 0 and self.value == 0: 171 | text = fnt.render(str(self.temp), 1, (128,128,128)) 172 | win.blit(text, (x+5, y+5)) 173 | elif not(self.value == 0): 174 | text = fnt.render(str(self.value), 1, (0, 0, 0)) 175 | win.blit(text, (x + (gap/2 - text.get_width()/2), y + (gap/2 - text.get_height()/2))) 176 | 177 | if self.selected: 178 | pygame.draw.rect(win, (255,0,0), (x,y, gap ,gap), 3) 179 | 180 | def draw_change(self, win, g=True): 181 | fnt = pygame.font.SysFont("comicsans", 40) 182 | 183 | gap = self.width / 9 184 | x = self.col * gap 185 | y = self.row * gap 186 | 187 | pygame.draw.rect(win, (255, 255, 255), (x, y, gap, gap), 0) 188 | 189 | text = fnt.render(str(self.value), 1, (0, 0, 0)) 190 | win.blit(text, (x + (gap / 2 - text.get_width() / 2), y + (gap / 2 - text.get_height() / 2))) 191 | if g: 192 | pygame.draw.rect(win, (0, 255, 0), (x, y, gap, gap), 3) 193 | else: 194 | pygame.draw.rect(win, (255, 0, 0), (x, y, gap, gap), 3) 195 | 196 | def set(self, val): 197 | self.value = val 198 | 199 | def set_temp(self, val): 200 | self.temp = val 201 | 202 | 203 | def find_empty(bo): 204 | for i in range(len(bo)): 205 | for j in range(len(bo[0])): 206 | if bo[i][j] == 0: 207 | return (i, j) # row, col 208 | 209 | return None 210 | 211 | 212 | def valid(bo, num, pos): 213 | # Check row 214 | for i in range(len(bo[0])): 215 | if bo[pos[0]][i] == num and pos[1] != i: 216 | return False 217 | 218 | # Check column 219 | for i in range(len(bo)): 220 | if bo[i][pos[1]] == num and pos[0] != i: 221 | return False 222 | 223 | # Check box 224 | box_x = pos[1] // 3 225 | box_y = pos[0] // 3 226 | 227 | for i in range(box_y*3, box_y*3 + 3): 228 | for j in range(box_x * 3, box_x*3 + 3): 229 | if bo[i][j] == num and (i,j) != pos: 230 | return False 231 | 232 | return True 233 | 234 | 235 | def redraw_window(win, board, time, strikes): 236 | win.fill((255,255,255)) 237 | # Draw time 238 | fnt = pygame.font.SysFont("comicsans", 40) 239 | text = fnt.render("Time: " + format_time(time), 1, (0,0,0)) 240 | win.blit(text, (540 - 160, 560)) 241 | # Draw Strikes 242 | text = fnt.render("X " * strikes, 1, (255, 0, 0)) 243 | win.blit(text, (20, 560)) 244 | # Draw grid and board 245 | board.draw() 246 | 247 | 248 | def format_time(secs): 249 | sec = secs%60 250 | minute = secs//60 251 | hour = minute//60 252 | 253 | mat = " " + str(minute) + ":" + str(sec) 254 | return mat 255 | 256 | 257 | def main(): 258 | win = pygame.display.set_mode((540,600)) 259 | pygame.display.set_caption("Sudoku") 260 | board = Grid(9, 9, 540, 540, win) 261 | key = None 262 | run = True 263 | start = time.time() 264 | strikes = 0 265 | while run: 266 | 267 | play_time = round(time.time() - start) 268 | 269 | for event in pygame.event.get(): 270 | if event.type == pygame.QUIT: 271 | run = False 272 | if event.type == pygame.KEYDOWN: 273 | if event.key == pygame.K_1: 274 | key = 1 275 | if event.key == pygame.K_2: 276 | key = 2 277 | if event.key == pygame.K_3: 278 | key = 3 279 | if event.key == pygame.K_4: 280 | key = 4 281 | if event.key == pygame.K_5: 282 | key = 5 283 | if event.key == pygame.K_6: 284 | key = 6 285 | if event.key == pygame.K_7: 286 | key = 7 287 | if event.key == pygame.K_8: 288 | key = 8 289 | if event.key == pygame.K_9: 290 | key = 9 291 | if event.key == pygame.K_KP1: 292 | key = 1 293 | if event.key == pygame.K_KP2: 294 | key = 2 295 | if event.key == pygame.K_KP3: 296 | key = 3 297 | if event.key == pygame.K_KP4: 298 | key = 4 299 | if event.key == pygame.K_KP5: 300 | key = 5 301 | if event.key == pygame.K_KP6: 302 | key = 6 303 | if event.key == pygame.K_KP7: 304 | key = 7 305 | if event.key == pygame.K_KP8: 306 | key = 8 307 | if event.key == pygame.K_KP9: 308 | key = 9 309 | if event.key == pygame.K_DELETE: 310 | board.clear() 311 | key = None 312 | 313 | if event.key == pygame.K_SPACE: 314 | board.solve_gui() 315 | 316 | if event.key == pygame.K_RETURN: 317 | i, j = board.selected 318 | if board.cubes[i][j].temp != 0: 319 | if board.place(board.cubes[i][j].temp): 320 | print("Success") 321 | else: 322 | print("Wrong") 323 | strikes += 1 324 | key = None 325 | 326 | if board.is_finished(): 327 | print("Game over") 328 | 329 | if event.type == pygame.MOUSEBUTTONDOWN: 330 | pos = pygame.mouse.get_pos() 331 | clicked = board.click(pos) 332 | if clicked: 333 | board.select(clicked[0], clicked[1]) 334 | key = None 335 | 336 | if board.selected and key != None: 337 | board.sketch(key) 338 | 339 | redraw_window(win, board, play_time, strikes) 340 | pygame.display.update() 341 | 342 | 343 | main() 344 | pygame.quit() 345 | --------------------------------------------------------------------------------