300 lines
10 KiB
Python
300 lines
10 KiB
Python
"""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
|