├── README.md ├── SudokuGenerator.py └── SudokuSolver.py /README.md: -------------------------------------------------------------------------------- 1 | # SudokuGenerator 2 | 3 | There are two files: 4 | * SudokuSolver.py is just a Solver, that finds all possible Solutions to a given Sudoku 5 | * SudokuGenerator.py generates Sudoku's with different difficulty levels (1 to 6). You can pass the difficulty level as an argument. For example: 6 | ``` 7 | python3 SudokuGenerator.py 4 8 | ``` 9 | 10 | ## Copyright 11 | 12 | Copyright ©️ 2023 Wilhelm Drehling, Heise Medien GmbH & Co. KG 13 | 14 | This program is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | This program is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | -------------------------------------------------------------------------------- /SudokuGenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import random 4 | import sys 5 | import time 6 | from datetime import datetime 7 | 8 | class Sudoku: 9 | def __init__(self): 10 | self.reset() 11 | 12 | def reset(self): 13 | # create empty 9x9 board 14 | rows = 9 15 | columns = 9 16 | self.board = [[0 for j in range(columns)] for i in range(rows)] 17 | 18 | 19 | def toSVG(self): 20 | # Variables 21 | cell_size = 40 22 | line_color = "black" 23 | 24 | # creating a rectangle in white with the size of a 9x9-Sudoku 25 | svg = '' 26 | svg += f'' 27 | 28 | # Draw the grid lines 29 | for i in range(10): 30 | line_width = 2 if i % 3 == 0 else 0.5 31 | # row lines 32 | svg += f'' 34 | # column lines 35 | svg += f'' 37 | 38 | # Draw the numbers 39 | for row in range(9): 40 | for column in range(9): 41 | if self.board[row][column] != 0: 42 | svg += f' {str(self.board[row][column])} ' 44 | 45 | svg += '' 46 | return svg 47 | 48 | def generate(self, difficulty): 49 | # fill diagonal squares 50 | for i in range(0, 9, 3): 51 | square = [1, 2, 3, 4, 5, 6, 7, 8, 9] 52 | random.shuffle(square) 53 | for r in range(3): 54 | for c in range(3): 55 | self.board[r + i][c + i] = square.pop() 56 | 57 | # fill rest 58 | for solutions in self.solve(): 59 | break 60 | 61 | # difficulty 62 | empty_cells = self.evaluate(difficulty) 63 | 64 | # creating a list of coordinates to visit and shuffeling them 65 | unvisited = [(r, c) for r in range(9) for c in range(9)] 66 | random.shuffle(unvisited) 67 | 68 | # remove numbers 69 | while empty_cells > 0 and len(unvisited) > 0: 70 | # saving a copy of the number, just in case, if we cant remove it 71 | r, c = unvisited.pop() 72 | copy = self.board[r][c] 73 | self.board[r][c] = 0 74 | 75 | # checking how many solutions are in the board 76 | solutions = [solution for solution in self.solve()] 77 | 78 | # if there is more than one solution, we put the number back 79 | if len(solutions) > 1: 80 | self.board[r][c] = copy 81 | else: 82 | empty_cells -= 1 83 | 84 | # if unvisited is empty, but empty_cells not -> trying again 85 | if empty_cells > 0: 86 | print("No Sudoku found. Trying again.") 87 | return False 88 | else: 89 | return True 90 | 91 | 92 | 93 | 94 | def evaluate(self, difficulty): 95 | # 1 = really easy, 3 = middle, 6 = devilish (lowest number possible, takes a long time to calculate) 96 | empty_cells = [0, 25, 35, 45, 52, 58, 64] 97 | if difficulty < 1 or difficulty > len(empty_cells)-1: 98 | print("invalid difficulty", file=sys.stderr) 99 | return empty_cells[difficulty] 100 | 101 | 102 | # method to print the board in console 103 | def print(self): 104 | for i in range(9): 105 | print(" ".join([str(x)if x != 0 else "." for x in self.board[i]])) 106 | 107 | 108 | def number_is_valid(self, row, column, number): 109 | # check row and column 110 | for i in range(9): 111 | if self.board[row][i] == number or self.board[i][column] == number: 112 | return False 113 | 114 | # check square 115 | start_column = column // 3 * 3 116 | start_row = row // 3 * 3 117 | for i in range(3): 118 | for j in range(3): 119 | if self.board[i + start_row][j + start_column] == number: 120 | return False 121 | return True 122 | 123 | 124 | def solve(self): 125 | # generate random numbers from 1 to 9 126 | digits = list(range(1, 10)) 127 | 128 | # find an empty cell 129 | for r in range(9): 130 | for c in range(9): 131 | if self.board[r][c] == 0: 132 | # for every empty cell fill a random valid number into it 133 | random.shuffle(digits) 134 | for n in digits: 135 | if self.number_is_valid(r, c, n): 136 | self.board[r][c] = n 137 | # is it solved? 138 | yield from self.solve() 139 | # backtrack 140 | self.board[r][c] = 0 141 | return 142 | yield True 143 | 144 | 145 | def main(): 146 | # takes difficulty as an argument, if not provided the program removes half of the board (level 3) 147 | args = [int(x) if x.isdecimal() else x for x in sys.argv[1:]] 148 | difficulty = args[0] if len(args) > 0 else 3 149 | 150 | sudoku = Sudoku() 151 | 152 | # trying in Total for 10 mins to find a sudoku 153 | timeout = 600 154 | start_time = time.time() 155 | end_time = start_time + timeout 156 | 157 | while time.time() < end_time: 158 | if sudoku.generate(difficulty) == True: 159 | break 160 | else: 161 | sudoku.reset() 162 | 163 | # printing 164 | sudoku.print() 165 | 166 | # creating the .svg-File with crrent date, time and difficulty 167 | svg = sudoku.toSVG() 168 | now = datetime.now() 169 | name = f'sudoku-{now:%Y%m%dT%H%M%S}-{difficulty}.svg' 170 | with open(name, 'w') as f: 171 | f.write(svg) 172 | 173 | 174 | if __name__ == "__main__": 175 | main() 176 | -------------------------------------------------------------------------------- /SudokuSolver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class Sudoku: 4 | def __init__(self, board): 5 | self.board = board 6 | 7 | # evaluates the difficulty of the sudoku by counting the empty spaces 8 | def evaluate(self): 9 | empty_cells = sum(self.board, []).count(0) 10 | if empty_cells <= 28: 11 | return "Pretty easy" 12 | elif empty_cells <= 39: 13 | return "Easy" 14 | elif empty_cells <= 53: 15 | return "Medium" 16 | elif empty_cells <= 64: 17 | return "Hard" 18 | elif empty_cells <= 71: 19 | return "Pretty hard" 20 | else: 21 | return "Diabolical" 22 | 23 | 24 | # method to print the board 25 | def print(self): 26 | for i in range(9): 27 | print(" ".join([str(x)if x != 0 else "." for x in self.board[i]])) 28 | 29 | 30 | def number_is_valid(self, row, column, number): 31 | # check row and column 32 | for i in range(9): 33 | if self.board[row][i] == number or self.board[i][column] == number: 34 | return False 35 | 36 | # check square 37 | start_column = column // 3 * 3 38 | start_row = row // 3 * 3 39 | for i in range(3): 40 | for j in range(3): 41 | if self.board[i + start_row][j + start_column] == number: 42 | return False 43 | return True 44 | 45 | 46 | def solve(self): 47 | # find an empty cell 48 | for r in range(9): 49 | for c in range(9): 50 | if self.board[r][c] == 0: 51 | # for every empty cell fill a valid number into it 52 | for n in range(1, 10): 53 | if self.number_is_valid(r, c, n): 54 | self.board[r][c] = n 55 | # is it solved? 56 | yield from self.solve() 57 | # backtrack 58 | self.board[r][c] = 0 59 | return False 60 | yield True 61 | 62 | def main(): 63 | # example board, 4 possible Solutions 64 | board = [[0, 7, 6, 0, 1, 3, 0, 0, 0], 65 | [0, 4, 0, 0, 0, 0, 0, 0, 0], 66 | [0, 0, 8, 6, 9, 0, 7, 0, 0], 67 | [0, 5, 0, 0, 6, 9, 0, 3, 0], 68 | [0, 0, 0, 0, 0, 0, 5, 4, 0], 69 | [0, 8, 0, 7, 3, 0, 0, 0, 0], 70 | [5, 1, 0, 0, 2, 6, 8, 0, 0], 71 | [0, 0, 7, 1, 0, 0, 9, 0, 0], 72 | [0, 0, 0, 0, 4, 0, 0, 6, 0]] 73 | 74 | sudoku = Sudoku(board) 75 | print("Difficulty: " + sudoku.evaluate()) 76 | 77 | # needed for multiple solutions 78 | counter = 0 79 | for solutions in sudoku.solve(): 80 | print() 81 | sudoku.print() 82 | counter += 1 83 | print("Solutions: ", counter) 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | --------------------------------------------------------------------------------