"""This module contains the Board class. It is used to represent different elements for the whole board. """ from typing import List from square import Square class Board: """The Board class contains different elements of the board.""" def __init__(self, string: str, debug=False): self.string = string self.debug = debug self.lines: List[set] = self.__compute_line_values() self.__debug("Initialized lines:") for i, line in enumerate(self.lines): self.__debug(f"Line n°{i}: {line}") self.__debug_pause("Continue") self.columns: List[set] = self.__compute_column_values() self.__debug("Initialized columns:") for i, column in enumerate(self.columns): self.__debug(f"Column n°{i}: {column}") self.__debug_pause("Continue") self.grids: List[set] = self.__compute_grid_values() self.__debug("Initialized grids:") for i, grid in enumerate(self.grids): self.__debug(f"Grid n°{i}: {grid}") self.__debug_pause("Continue") self.squares: List[Square] = self.__compute_square_values() self.__debug("Initialized squares:") for i, square in enumerate(self.squares): self.__debug(f"Square n°{i} {square.coordinates}: {square.values}") self.__debug_pause("Continue") def __debug(self, string: str): if self.debug: print(string) def __debug_pause(self, string: str = ""): if self.debug: input(string) def __debug_print_board(self): if self.debug: print(self) def __str__(self): output = "" for lin_nb, line in enumerate(self.lines): if (lin_nb) % 3 == 0: output += "+-------+-------+-------+\n" for col_nb, square in enumerate(line): if (col_nb) % 3 == 0: output += '| ' if isinstance(square, set): if len(square) == 1: output += str(square)[2:-2] else: output += '.' elif isinstance(square, Square): if len(square.values) == 1: output += str(square.values)[2:-2] else: output += '.' else: output += square output += ' ' output += '|\n' output += "+-------+-------+-------+\n" return output def __compute_line_values(self): """Return a list of sets containing the values in each line.""" lines: List[str] = [""] * 9 i = 0 line_nb = 0 while line_nb < 9: if self.string[i] == '\n': line_nb += 1 elif self.string[i] != '.': lines[line_nb] += self.string[i] i += 1 output: List[set] = [set(values) for values in lines] # Make sure the sets reflect the strings for i in range(9): assert len(output[i]) == len(lines[i]) return output def __compute_column_values(self): """Return a list of sets containing the values in each column. """ columns: List[str] = [""] * 9 string: str = self.string.replace('\n', '') assert len(string) == 81 i: int = 0 column_nb: int = i % 9 while i < 81: if string[i] != '.': columns[column_nb] += string[i] i += 1 column_nb = i % 9 output: List[set] = [set(values) for values in columns] # Make sure the sets reflect the strings for i in range(9): assert len(output[i]) == len(columns[i]) return output def __compute_grid_values(self): """Return a list of sets containing the values in each grid. """ grids: List[str] = [""] * 9 lines: List[str] = self.string.split('\n')[:-1] assert len(lines) == 9 grid_mappings = [ (0, 0), # grid 0 (0, 1), # grid 1 (0, 2), # grid 2 (1, 0), # grid 3 (1, 1), # grid 4 (1, 2), # grid 5 (2, 0), # grid 6 (2, 1), # grid 7 (2, 2), # grid 8 ] for line_nb, line in enumerate(lines): for column_nb in range(9): if line[column_nb] != '.': grid_coordinates = (line_nb // 3, column_nb // 3) # Find the grid that the current character belongs to grid_nb: int = 0 while grid_mappings[grid_nb] != grid_coordinates: grid_nb += 1 if grid_nb == 9: raise AssertionError("Board is invalid") grids[grid_nb] += line[column_nb] output: List[set] = [set(values) for values in grids] # Make sure the sets reflect the strings for i in range(9): assert len(output[i]) == len(grids[i]) return output def __compute_square_values(self): """Return a list of Square objects.""" squares: List[Square] = [] lines: List[str] = self.string.split('\n')[:-1] assert len(lines) == 9 for line_nb, line in enumerate(lines): for column_nb in range(9): if line[column_nb] == '.': square = Square(line_nb, column_nb, set()) else: square = Square(line_nb, column_nb, set(line[column_nb])) squares.append(square) return squares def compute_possible_values(self): """Compute possible values for each square in the board.""" self.__debug(">>>Compute possible values") self.__debug_pause() for square_nb, square in enumerate(self.squares): if len(square.values) == 0: for number in range(1, 10): if str(number) not in self.lines[square.line] and \ str(number) not in self.columns[square.column] and \ str(number) not in self.grids[square.grid]: self.squares[square_nb].values.add(str(number)) self.__debug( f"Square n°{square_nb} {square.coordinates}: {square.values}" ) self.__debug_print_board() def compute_obligated_in_each_line(self): """Find obligated values per line for every number. Go through each line. For each square, go through numbers 1 to 9. If the number is in the square's values but not in the values of the rest of the squares in the line then the square should take that number. """ self.__debug(">>>Compute obligated in each line") self.__debug_pause() for line_id, line in enumerate(self.lines): self.__debug(f"Line n°{line_id}") for square_id, square in enumerate(line): self.__debug(f" square n°{square_id}, {square.values}") # Create an empty set to store the other square's values. other_values = set() for other_square_id in range(9): # ignore current square if other_square_id != square_id: # update other_values with values of other squares other_values = other_values.union( line[other_square_id].values ) self.__debug(f" other values: {other_values}") for number in range(1, 10): if str(number) in square.values and \ str(number) not in other_values: self.__debug(f" {number}") self.lines[line_id][square_id].values = {str(number)} self.__debug_print_board() def compute_obligated_in_each_column(self): """Find obligated values per column for every number. Go through each column. For each square, go through numbers 1 to 9. If the number is in the square's values but not in the values of the rest of the squares in the column then the square should take that number. """ self.__debug(">>>Compute obligated in each column") self.__debug_pause() for col_id, column in enumerate(self.columns): self.__debug(f"Column n°{col_id}") for square_id, square in enumerate(column): self.__debug(f" square n°{square_id}, {square.values}") # Create an empty set to store the other square's values. other_values = set() for other_square_id in range(9): # ignore current square if other_square_id != square_id: # update other_values with values of other squares other_values = other_values.union( column[other_square_id].values ) self.__debug(f" other values: {other_values}") for number in range(1, 10): if str(number) in square.values and \ str(number) not in other_values: self.__debug(f" {number}") self.columns[col_id][square_id].values = {str(number)} self.__debug_print_board() def get_new_board(self) -> str: """Return the board in its current state as a string. This methods let's us recreate a new board from a string so we can reiterate throught the solving process. """ self.__debug(">>>Get new board") self.__debug_pause() output = "" column = 0 for square in self.squares: if len(square.values) == 1: output += str(square.values)[2:-2] else: output += '.' column += 1 if column == 9: output += '\n' column = 0 return output