def is_partial_solvable(self, sudoku): """Whether the sudoku can be solved by partial solver.""" clone = sudoku_data.SudokuData() clone.copy(sudoku) for _ in range(80): partial_solution = self._solver.solve(clone, partial=True) if not partial_solution: break return clone.is_solved()
def __init__(self, randomize_type='random'): self._sudoku = sudoku_data.SudokuData() self._possible_values = [[set()] * 9 for _ in range(9)] # A list grouping locations by the number of possible values. self._location_groups = [set()] * 10 # A dictionary mapping the possible locations of a number in a region. self._possible_locations = {} # A dictionary of unique locations for a number in a region. # The key is a tupe of the region type, region number, the value (number), # and the value is the only possible location. self._unique_locations = {} # Type of ranomizing, can be random, min or max. self.randomize_type = randomize_type
def generate_sudoku(self): """Generates a new sudoku and add it to the correct level.""" min_nr_sudoku = min( [len(sudoku) for sudoku in self._sudoku_map.values()]) # We already have enough sudoku in the cache. if min_nr_sudoku > 10: return nr_spaces = 56 sudoku = sudoku_data.SudokuData() self._solver.solve(sudoku) full_sudoku = sudoku_data.SudokuData() full_sudoku.copy(sudoku) nr_removed = 0 while nr_removed < nr_spaces: row = random.randrange(9) col = random.randrange(9) if sudoku.get(row, col) != ' ': sudoku.set(row, col, ' ') nr_removed += 1 self.make_one_solution(sudoku, full_sudoku) curr_level = self.get_sudoku_level(sudoku) sudoku_list = self._sudoku_map[curr_level] if len(sudoku_list) < 100: sudoku_list.append(sudoku)
def make_one_solution(self, sudoku, full_sudoku): """Make a sudoku has only one solution.""" for _ in range(80): clone1 = sudoku_data.SudokuData() clone1.copy(sudoku) self._max_solver.solve(clone1) clone2 = sudoku_data.SudokuData() clone2.copy(sudoku) self._min_solver.solve(clone2) is_same = True start_row = random.randrange(9) start_col = random.randrange(9) for i in range(9): for j in range(9): row = int((start_row + i) % 9) col = int((start_col + j) % 9) if clone1.get(row, col) != clone2.get(row, col): is_same = False sudoku.set(row, col, full_sudoku.get(row, col)) break if not is_same: break if is_same: return
def read_data_file(file_name): expected_solutions = [] sudoku = sudoku_data.SudokuData() with open(file_name, 'r') as f: lines = f.read().split('\n') sudoku.from_lines(lines) nr_solutions = int(lines[9]) if nr_solutions == -1: expected_solutions = None else: for i in range(nr_solutions): expected_solutions.append(lines[i + 10]) if expected_solutions is None: sorted_solutions = None else: sorted_solutions = sorted(expected_solutions) return sudoku, sorted_solutions
def __init__(self, stdscr): self.stdscr = stdscr self.height = 18 self.width = 36 self.curr_row = 4 self.curr_col = 4 self.curr_color = 1 self.message = None self.confirm = None self.mouse_x = None self.mouse_y = None self.level = 'Easy' self.sudoku = sudoku_data.SudokuData() self.solver = sudoku_solver.SudokuSolver() self.generator = sudoku_generator.SudokuGenerator() self._setup_colors() self.data_file = '/tmp/magic_sudoku.data' self.changes = [] self.redo_changes = []
def test_solver(path, type): if type == 'partial': partial = True simple = False elif type == 'simple': partial = False simple = True else: partial = False simple = False for file_name in os.listdir(path): full_name = os.path.join(path, file_name) sudoku, expected_solution = read_data_file(full_name) original = sudoku_data.SudokuData() original.copy(sudoku) solution = sudoku_solver.SudokuSolver().solve(sudoku, partial=partial, simple=simple) compare_solutions(full_name, solution, expected_solution) compare_sudoku(full_name, sudoku, original, solution) print('Tests in {!r} with type {!r} passed.'.format(path, type))
def _process_key(self, key): """Process the key and mouse events.""" if self.message: if self.message == _WIN_MSG: self.message = _NEW_SUDOKU_MSG self.confirm = _NEW_SUDOKU_CONFIRM return self.message = None curses.curs_set(1) if self.confirm is not None: if self.confirm == _NEW_SUDOKU_CONFIRM: self.confirm = None if key == ord('0'): self.level = 'Easy' elif key == ord('1'): self.level = 'Easy' elif key == ord('2'): self.level = 'Medium' elif key == ord('3'): self.level = 'Hard' elif key == ord('4'): self.level = 'Challenger' else: return if key == ord('0'): self._change_sudoku(sudoku_data.SudokuData()) else: self._change_sudoku( self.generator.get_sudoku(level=self.level)) elif key == ord('-') or key == ord('_'): # Reduce size of the sudoku board. if self.height > 18: self.height -= 9 self.width -= 18 elif key == ord('+') or key == ord('='): # Increase size of the sudoku board. self.height += 9 self.width += 18 elif key == curses.KEY_DOWN: # Move cursor down. self.curr_row = min(8, self.curr_row + 1) elif key == curses.KEY_UP: # Move cursor up. self.curr_row = max(0, self.curr_row - 1) elif key == curses.KEY_RIGHT: # Move cursor right. self.curr_col = min(8, self.curr_col + 1) elif key == curses.KEY_LEFT: # Move cursor left. self.curr_col = max(0, self.curr_col - 1) elif key == curses.KEY_MOUSE: # Move cursor to the location of the mouse. try: _, self.mouse_x, self.mouse_y, _, _ = curses.getmouse() except Exception: curses.beep() elif key == ord('a') or key == ord('A'): # Automatically solve the sudoku. clone = sudoku_data.SudokuData() clone.copy(self.sudoku) solution = self.solver.solve(clone) if solution: self._change_color(self.curr_color + 1) for row, col, value in solution: self._change_number(row, col, value) else: self.message = 'Not solvable' elif key == ord('h') or key == ord('H'): # Give hint of the next move. clone = sudoku_data.SudokuData() clone.copy(self.sudoku) solution = self.solver.solve(clone, partial=True) if not solution: solution = self.solver.solve(clone) if solution: for row, col, value in solution: self._change_number(row, col, value) break else: self.message = 'Not solvable' elif key == ord('n') or key == ord('N'): # Generates a new sudoku. self.message = _NEW_SUDOKU_MSG self.confirm = _NEW_SUDOKU_CONFIRM elif key == ord('c') or key == ord('C'): # Change current color use for new numbers fill in the board. self._change_color(self.curr_color + 1) elif key == ord('b') or key == ord('B'): # Change current color to previous one. self._change_color(self.curr_color - 1) elif key == ord('m') or key == ord('M'): # Show or hide menu. self.message = _MENU elif key == ord('u') or key == ord('U'): # Undo changes. if self.changes: change_type, content = self.changes[-1] if change_type == _NUMBER_CHANGE: row, col, original_value, _ = content self.sudoku.set(row, col, original_value) self.curr_row = row self.curr_col = col elif change_type == _COLOR_CHANGE: original_color, _ = content self.curr_color = original_color else: (original_sudoku, original_colors, original_curr_color), _ = content self.sudoku = original_sudoku self.colors = original_colors self.curr_color = original_curr_color del self.changes[-1] self.redo_changes.append((change_type, content)) self._auto_save() else: self.message = 'Nothing to undo' elif key == ord('r') or key == ord('R'): # Redo changes. if self.redo_changes: change_type, content = self.redo_changes[-1] if change_type == _NUMBER_CHANGE: row, col, _, new_value = content self.sudoku.set(row, col, new_value) self.curr_row = row self.curr_col = col elif change_type == _COLOR_CHANGE: _, new_color = content self.curr_color = new_color else: _, (new_sudoku, new_colors, new_curr_color) = content self.sudoku = new_sudoku self.colors = new_colors self.curr_color = new_curr_color del self.redo_changes[-1] self.changes.append((change_type, content)) self._auto_save() else: self.message = 'Nothing to redo' elif key >= ord('1') and key <= ord('9') or key == ord(' '): # Fill in a new number in the board. Space erases existing number. if self._change_number(self.curr_row, self.curr_col, chr(key)) and self.sudoku.is_solved(): self.message = _WIN_MSG