2019-11-30 13:55:22 +01:00
|
|
|
"""This module contains the Board class.
|
|
|
|
|
|
|
|
It is used to represent different elements for the whole board.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from square import Square
|
|
|
|
|
|
|
|
|
|
|
|
class Board:
|
|
|
|
"""The Board class contains different elements of the board."""
|
|
|
|
|
2019-12-01 19:27:38 +01:00
|
|
|
def __init__(self, string, debug=True):
|
|
|
|
self.debug = debug
|
2019-11-30 13:55:22 +01:00
|
|
|
self.lines = [""] * 9
|
|
|
|
i = 0
|
|
|
|
line = 0
|
|
|
|
while line < 9:
|
|
|
|
if string[i] == '\n':
|
|
|
|
line += 1
|
|
|
|
else:
|
|
|
|
self.lines[line] += string[i]
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
self.columns = [""] * 9
|
|
|
|
for col in range(9):
|
|
|
|
for line in self.lines:
|
|
|
|
self.columns[col] += line[col]
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
self.grids = self.__compute_grid_values()
|
|
|
|
self.squares = []
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
self.__assert_lines_and_columns_have_same_values()
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
def __debug(self, string: str):
|
2019-12-01 19:27:38 +01:00
|
|
|
if self.debug:
|
|
|
|
print(string)
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
def __debug_pause(self, string: str = ""):
|
|
|
|
if self.debug:
|
|
|
|
input(string)
|
|
|
|
|
|
|
|
def __debug_print_board(self):
|
|
|
|
if self.debug:
|
|
|
|
print(self)
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
def __str__(self):
|
2019-11-30 15:21:33 +01:00
|
|
|
output = ""
|
2019-12-01 19:27:38 +01:00
|
|
|
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 += '| '
|
2019-11-30 17:52:45 +01:00
|
|
|
if isinstance(square, set):
|
|
|
|
if len(square) == 1:
|
2019-12-02 08:51:47 +01:00
|
|
|
output += str(square)[2:-2]
|
2019-11-30 15:21:33 +01:00
|
|
|
else:
|
|
|
|
output += '.'
|
2019-11-30 17:52:45 +01:00
|
|
|
elif isinstance(square, Square):
|
|
|
|
if len(square.values) == 1:
|
|
|
|
output += str(square.values)[2:-2]
|
2019-11-30 15:21:33 +01:00
|
|
|
else:
|
|
|
|
output += '.'
|
|
|
|
else:
|
2019-11-30 17:52:45 +01:00
|
|
|
output += square
|
2019-11-30 15:21:33 +01:00
|
|
|
output += ' '
|
2019-12-01 19:27:38 +01:00
|
|
|
output += '|\n'
|
|
|
|
|
|
|
|
output += "+-------+-------+-------+\n"
|
2019-11-30 15:21:33 +01:00
|
|
|
|
|
|
|
return output
|
2019-11-30 13:55:22 +01:00
|
|
|
|
|
|
|
def __assert_lines_and_columns_have_same_values(self):
|
2019-11-30 15:21:33 +01:00
|
|
|
differences = []
|
|
|
|
for lin in range(len(self.lines)):
|
|
|
|
for col in range(len(self.columns)):
|
|
|
|
if isinstance(self.lines[lin][col], Square):
|
2019-11-30 17:04:43 +01:00
|
|
|
line_values = self.lines[lin][col].values
|
|
|
|
column_values = self.columns[col][lin].values
|
2019-11-30 15:21:33 +01:00
|
|
|
else:
|
2019-11-30 17:04:43 +01:00
|
|
|
line_values = self.lines[lin][col]
|
|
|
|
column_values = self.columns[col][lin]
|
2019-11-30 15:21:33 +01:00
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
if line_values != column_values:
|
2019-11-30 15:21:33 +01:00
|
|
|
differences.append('X')
|
2019-11-30 13:55:22 +01:00
|
|
|
|
|
|
|
assert len(differences) == 0
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug("Lines and columns have the same values.")
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
def __compute_grid_values(self):
|
|
|
|
grids = [""] * 9
|
|
|
|
for lin, line in enumerate(self.lines):
|
|
|
|
for col in range(9):
|
|
|
|
if line[col] != '.':
|
|
|
|
if lin // 3 == 0 and col // 3 == 0:
|
|
|
|
grids[0] += line[col]
|
|
|
|
elif lin // 3 == 0 and col // 3 == 1:
|
|
|
|
grids[1] += line[col]
|
|
|
|
elif lin // 3 == 0 and col // 3 == 2:
|
|
|
|
grids[2] += line[col]
|
|
|
|
elif lin // 3 == 1 and col // 3 == 0:
|
|
|
|
grids[3] += line[col]
|
|
|
|
elif lin // 3 == 1 and col // 3 == 1:
|
|
|
|
grids[4] += line[col]
|
|
|
|
elif lin // 3 == 1 and col // 3 == 2:
|
|
|
|
grids[5] += line[col]
|
|
|
|
elif lin // 3 == 2 and col // 3 == 0:
|
|
|
|
grids[6] += line[col]
|
|
|
|
elif lin // 3 == 2 and col // 3 == 1:
|
|
|
|
grids[7] += line[col]
|
|
|
|
elif lin // 3 == 2 and col // 3 == 2:
|
|
|
|
grids[8] += line[col]
|
|
|
|
|
|
|
|
return [set(values) for values in grids]
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
def __compute_possible_values_from_lines(self):
|
|
|
|
"""Compute possible values for each number in a line.
|
|
|
|
|
|
|
|
Go through each line and add possible values to each square that hasn't
|
|
|
|
been found yet. The values are computed based on the fact that they
|
|
|
|
aren't already present in that same line.
|
|
|
|
"""
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
for lin, line in enumerate(self.lines):
|
|
|
|
self.lines[lin] = list(line)
|
2019-11-30 13:55:22 +01:00
|
|
|
for column in range(9):
|
|
|
|
if line[column] == '.':
|
2019-11-30 17:04:43 +01:00
|
|
|
self.lines[lin][column] = {
|
|
|
|
str(number) for number in range(1, 10)
|
2019-11-30 13:55:22 +01:00
|
|
|
if str(number) not in line
|
|
|
|
}
|
|
|
|
else:
|
2019-11-30 17:04:43 +01:00
|
|
|
self.lines[lin][column] = {line[column]}
|
2019-11-30 13:55:22 +01:00
|
|
|
|
|
|
|
def __compute_possible_values_from_columns(self):
|
|
|
|
"""Compute possible values for each number in a column.
|
|
|
|
|
|
|
|
Go through each column and add possible values to each square that
|
|
|
|
hasn't been found yet. The values are computed based on the fact that
|
|
|
|
they aren't already present in that same column.
|
|
|
|
"""
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
for col, column in enumerate(self.columns):
|
|
|
|
self.columns[col] = list(column)
|
2019-11-30 13:55:22 +01:00
|
|
|
for line in range(9):
|
|
|
|
if column[line] == '.':
|
2019-11-30 17:04:43 +01:00
|
|
|
self.columns[col][line] = {
|
|
|
|
str(number) for number in range(1, 10)
|
2019-11-30 13:55:22 +01:00
|
|
|
if str(number) not in column
|
|
|
|
}
|
|
|
|
else:
|
2019-11-30 17:04:43 +01:00
|
|
|
self.columns[col][line] = {column[line]}
|
2019-11-30 13:55:22 +01:00
|
|
|
|
|
|
|
def compute_possible_values(self):
|
|
|
|
"""Compute possible values for each square in the board."""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Compute possible values")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
self.__compute_possible_values_from_lines()
|
|
|
|
self.__compute_possible_values_from_columns()
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug("Possible values computed")
|
|
|
|
|
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-12-01 19:27:38 +01:00
|
|
|
def compute_obligated_in_each_line(self):
|
2019-12-01 20:11:17 +01:00
|
|
|
"""Find obligated values per line for every number.
|
2019-12-01 19:27:38 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Compute obligated in each line")
|
|
|
|
self.__debug_pause()
|
2019-12-01 19:27:38 +01:00
|
|
|
|
2019-12-01 20:11:17 +01:00
|
|
|
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}")
|
2019-12-01 19:27:38 +01:00
|
|
|
# Create an empty set to store the other square's values.
|
|
|
|
other_values = set()
|
2019-12-01 20:11:17 +01:00
|
|
|
for other_square_id in range(9):
|
|
|
|
# ignore current square
|
|
|
|
if other_square_id != square_id:
|
|
|
|
# update other_values with values of other squares
|
2019-12-01 19:27:38 +01:00
|
|
|
other_values = other_values.union(
|
2019-12-01 20:11:17 +01:00
|
|
|
line[other_square_id].values
|
2019-12-01 19:27:38 +01:00
|
|
|
)
|
|
|
|
|
2019-12-01 20:11:17 +01:00
|
|
|
self.__debug(f" other values: {other_values}")
|
2019-12-01 19:27:38 +01:00
|
|
|
|
|
|
|
for number in range(1, 10):
|
2019-12-01 20:11:17 +01:00
|
|
|
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)}
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-12-01 20:11:17 +01:00
|
|
|
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.
|
|
|
|
"""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Compute obligated in each column")
|
|
|
|
self.__debug_pause()
|
2019-12-01 20:11:17 +01:00
|
|
|
|
|
|
|
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)}
|
2019-12-01 19:27:38 +01:00
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
def create_squares(self):
|
|
|
|
"""Make each square of the board a Square object."""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Create squares")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
self.squares = []
|
|
|
|
|
|
|
|
if isinstance(self.lines[0][0], set):
|
|
|
|
for lin, line in enumerate(self.lines):
|
|
|
|
for col in range(len(self.columns)):
|
|
|
|
square = Square(lin, col, line[col])
|
|
|
|
self.lines[lin][col] = square
|
|
|
|
self.columns[col][lin] = square
|
|
|
|
self.squares.append(square)
|
2019-12-01 19:27:38 +01:00
|
|
|
self.__debug("Squares created")
|
2019-11-30 17:04:43 +01:00
|
|
|
|
|
|
|
self.__assert_lines_and_columns_have_same_values()
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-11-30 17:52:45 +01:00
|
|
|
def discard_with_grids(self):
|
|
|
|
"""Remove impossible values in squares, by discarding from grids."""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Discard with grids")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-12-01 20:11:17 +01:00
|
|
|
for square_index, square in enumerate(self.squares):
|
|
|
|
self.__debug(f" square n°{square_index}: {square.values}")
|
|
|
|
self.__debug(f" values in grid: {self.grids[square.grid_id]}")
|
2019-11-30 17:52:45 +01:00
|
|
|
if len(square.values) > 1:
|
|
|
|
for value in self.grids[square.grid_id]:
|
|
|
|
if value in square.values:
|
|
|
|
square.values.discard(value)
|
2019-12-01 20:11:17 +01:00
|
|
|
self.__debug(f" square n°{square_index}: {square.values}")
|
2019-11-30 17:52:45 +01:00
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-11-30 17:04:43 +01:00
|
|
|
def intersect_lines_and_columns(self):
|
2019-11-30 13:55:22 +01:00
|
|
|
"""Remove impossible values from possibilities in lines and columns.
|
|
|
|
|
|
|
|
Go through each line and for each square intersect the set with the set
|
|
|
|
in the same square in self.columns.
|
|
|
|
Go through each column and for each square intersect the set with the
|
|
|
|
set in the same square in self.lines.
|
|
|
|
|
|
|
|
Note: you need to run compute_possible_values first.
|
|
|
|
"""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Intersect lines and columns")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
for lin, line in enumerate(self.lines):
|
|
|
|
for col, column in enumerate(self.columns):
|
2019-12-01 19:27:38 +01:00
|
|
|
self.lines[lin][col] = line[col].intersection(column[lin])
|
|
|
|
self.columns[col][lin] = column[lin].intersection(line[col])
|
2019-11-30 13:55:22 +01:00
|
|
|
|
|
|
|
self.__assert_lines_and_columns_have_same_values()
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_print_board()
|
|
|
|
|
|
|
|
def get_new_board(self) -> str:
|
2019-11-30 17:52:45 +01:00
|
|
|
"""Return the board in its current state as a string.
|
2019-11-30 17:04:43 +01:00
|
|
|
|
2019-11-30 17:52:45 +01:00
|
|
|
This methods let's us recreate a new board from a string so we can
|
|
|
|
reiterate throught the solving process.
|
|
|
|
"""
|
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Get new board")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-11-30 17:52:45 +01:00
|
|
|
output = ""
|
|
|
|
column = 0
|
2019-11-30 17:04:43 +01:00
|
|
|
for square in self.squares:
|
2019-11-30 17:52:45 +01:00
|
|
|
if len(square.values) == 1:
|
|
|
|
output += str(square.values)[2:-2]
|
|
|
|
else:
|
|
|
|
output += '.'
|
|
|
|
column += 1
|
|
|
|
if column == 9:
|
|
|
|
output += '\n'
|
|
|
|
column = 0
|
|
|
|
|
|
|
|
return output
|