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.
|
2019-12-03 12:00:32 +01:00
|
|
|
It also contains methods to compute different possible values for each square
|
|
|
|
on the board.
|
2019-11-30 13:55:22 +01:00
|
|
|
"""
|
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
from typing import List, Optional, Tuple
|
2019-11-30 13:55:22 +01:00
|
|
|
from square import Square
|
|
|
|
|
|
|
|
|
|
|
|
class Board:
|
2019-12-03 12:00:32 +01:00
|
|
|
"""The Board class contains different elements of the board.
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-03 12:00:32 +01:00
|
|
|
When the board is instantiated, it has the following attributes:
|
|
|
|
self.string The original string that was passed in.
|
|
|
|
self.debug (bool) Gives verbose output.
|
|
|
|
|
|
|
|
The four following attributes are computed from self.string:
|
|
|
|
|
|
|
|
self.lines List of 9 sets, each containing the values on the board
|
|
|
|
at that line number.
|
|
|
|
self.columns List of 9 sets, each containing the values on the board
|
|
|
|
at that column number.
|
|
|
|
self.grids List of 9 sets, each containing the values on the board
|
|
|
|
at that grid number.
|
|
|
|
self.squares List containing all 81 Squares of the board.
|
|
|
|
|
|
|
|
--
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, string: str, debug: bool = False):
|
|
|
|
self.string: str = string
|
|
|
|
self.debug: bool = debug
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
self.lines: List[set] = self.__compute_line_values()
|
2019-12-02 15:21:49 +01:00
|
|
|
self.__debug("Initialized lines:")
|
|
|
|
for i, line in enumerate(self.lines):
|
|
|
|
self.__debug(f"Line n°{i}: {line}")
|
2019-12-02 17:34:39 +01:00
|
|
|
self.__debug_pause("Continue")
|
2019-12-02 15:21:49 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
self.columns: List[set] = self.__compute_column_values()
|
2019-12-02 15:21:49 +01:00
|
|
|
self.__debug("Initialized columns:")
|
|
|
|
for i, column in enumerate(self.columns):
|
|
|
|
self.__debug(f"Column n°{i}: {column}")
|
2019-12-02 17:34:39 +01:00
|
|
|
self.__debug_pause("Continue")
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
self.grids: List[set] = self.__compute_grid_values()
|
2019-12-02 15:21:49 +01:00
|
|
|
self.__debug("Initialized grids:")
|
|
|
|
for i, grid in enumerate(self.grids):
|
|
|
|
self.__debug(f"Grid n°{i}: {grid}")
|
2019-12-02 17:34:39 +01:00
|
|
|
self.__debug_pause("Continue")
|
2019-11-30 17:04:43 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
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")
|
2019-11-30 13:55:22 +01:00
|
|
|
|
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-12-03 09:06:16 +01:00
|
|
|
def __str__(self) -> str:
|
2019-12-03 12:00:32 +01:00
|
|
|
self.__debug(self.string)
|
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
output: str = ""
|
2019-12-03 12:00:32 +01:00
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
for square_nb, square in enumerate(self.squares):
|
|
|
|
assert isinstance(square, Square)
|
|
|
|
# square_nb // 9 gives us line number
|
|
|
|
if (square_nb // 9) % 3 == 0 and square_nb % 9 == 0:
|
2019-12-01 19:27:38 +01:00
|
|
|
output += "+-------+-------+-------+\n"
|
2019-12-03 09:06:16 +01:00
|
|
|
# square_nb % 9 gives us column number
|
|
|
|
if (square_nb % 9) % 3 == 0:
|
|
|
|
output += '| '
|
|
|
|
if len(square.values) == 1:
|
|
|
|
# example : "{'6'}" -> take away {''}
|
|
|
|
output += str(square.values)[2:-2]
|
|
|
|
else:
|
|
|
|
output += '.'
|
|
|
|
output += ' '
|
|
|
|
if square_nb % 9 == 8:
|
|
|
|
output += '|\n'
|
2019-12-01 19:27:38 +01:00
|
|
|
|
|
|
|
output += "+-------+-------+-------+\n"
|
2019-11-30 15:21:33 +01:00
|
|
|
|
|
|
|
return output
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
def __compute_line_values(self) -> List[set]:
|
2019-12-02 15:21:49 +01:00
|
|
|
"""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]
|
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
# Make sure the sets reflect the strings
|
2019-12-02 15:21:49 +01:00
|
|
|
for i in range(9):
|
|
|
|
assert len(output[i]) == len(lines[i])
|
|
|
|
|
|
|
|
return output
|
2019-11-30 15:21:33 +01:00
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
def __compute_column_values(self) -> List[set]:
|
2019-12-03 12:00:32 +01:00
|
|
|
"""Return a list of sets containing the values in each column."""
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 15:21:49 +01:00
|
|
|
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
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 15:21:49 +01:00
|
|
|
output: List[set] = [set(values) for values in columns]
|
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
# Make sure the sets reflect the strings
|
2019-12-02 15:21:49 +01:00
|
|
|
for i in range(9):
|
|
|
|
assert len(output[i]) == len(columns[i])
|
|
|
|
|
|
|
|
return output
|
2019-12-02 08:51:47 +01:00
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
def __compute_grid_values(self) -> List[set]:
|
2019-12-03 12:00:32 +01:00
|
|
|
"""Return a list of sets containing the values in each grid."""
|
2019-12-02 15:21:49 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
# Find the grid that the current character belongs to
|
2019-12-02 15:21:49 +01:00
|
|
|
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]
|
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
# Make sure the sets reflect the strings
|
2019-12-02 15:21:49 +01:00
|
|
|
for i in range(9):
|
|
|
|
assert len(output[i]) == len(grids[i])
|
|
|
|
|
|
|
|
return output
|
2019-11-30 17:04:43 +01:00
|
|
|
|
2019-12-03 09:06:16 +01:00
|
|
|
def __compute_square_values(self) -> List[Square]:
|
2019-12-02 17:34:39 +01:00
|
|
|
"""Return a list of Square objects."""
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
squares: List[Square] = []
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
lines: List[str] = self.string.split('\n')[:-1]
|
|
|
|
assert len(lines) == 9
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
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]))
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
squares.append(square)
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
return squares
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
def __compute_obligated_in_each_line(self):
|
|
|
|
"""Find obligated values per line for every number."""
|
|
|
|
|
|
|
|
self.__debug(">>>Compute obligated in each line")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
|
|
|
for line_nb in range(9):
|
|
|
|
self.__debug(f"Line n°{line_nb}")
|
|
|
|
for column_nb in range(9):
|
|
|
|
square, square_nb = self.get_square(line_nb, column_nb)
|
|
|
|
self.__debug(f" square n°{square_nb}, {square.values}")
|
|
|
|
|
|
|
|
# Create an empty set to store the other square's values.
|
|
|
|
other_values = set()
|
|
|
|
|
|
|
|
other_squares = [
|
|
|
|
self.get_square(line_nb, other_square_nb)[0]
|
|
|
|
for other_square_nb in range(9)
|
|
|
|
if other_square_nb != column_nb
|
|
|
|
]
|
|
|
|
|
|
|
|
for other_square in other_squares:
|
|
|
|
# update other_values with values of other squares
|
|
|
|
other_values = other_values.union(other_square.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" current number: {number}")
|
|
|
|
self.squares[square_nb].values = {str(number)}
|
|
|
|
|
|
|
|
self.__debug_print_board()
|
|
|
|
|
|
|
|
def __compute_obligated_in_each_column(self):
|
|
|
|
"""Find obligated values per column for every number."""
|
|
|
|
|
|
|
|
self.__debug(">>>Compute obligated in each column")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
|
|
|
for column_nb in range(9):
|
|
|
|
self.__debug(f"Column n°{column_nb}")
|
|
|
|
for line_nb in range(9):
|
|
|
|
square, square_nb = self.get_square(line_nb, column_nb)
|
|
|
|
self.__debug(f" square n°{square_nb}, {square.values}")
|
|
|
|
|
|
|
|
# Create an empty set to store the other square's values.
|
|
|
|
other_values = set()
|
|
|
|
|
|
|
|
other_squares = [
|
|
|
|
self.get_square(other_square_nb, column_nb)[0]
|
|
|
|
for other_square_nb in range(9)
|
|
|
|
if other_square_nb != line_nb
|
|
|
|
]
|
|
|
|
|
|
|
|
for other_square in other_squares:
|
|
|
|
# update other_values with values of other squares
|
|
|
|
other_values = other_values.union(other_square.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" current number: {number}")
|
|
|
|
self.squares[square_nb].values = {str(number)}
|
|
|
|
|
|
|
|
self.__debug_print_board()
|
|
|
|
|
|
|
|
def __compute_obligated_in_each_grid(self):
|
|
|
|
"""Find obligated values per grid for every number."""
|
|
|
|
|
|
|
|
self.__debug(">>>Compute obligated in each grid")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
|
|
|
for grid_nb in range(9):
|
|
|
|
self.__debug(f"Grid n°{grid_nb}")
|
|
|
|
for square_nb, square in enumerate(self.squares):
|
|
|
|
if square.grid == grid_nb:
|
|
|
|
self.__debug(f" square n°{square_nb}, {square.values}")
|
|
|
|
|
|
|
|
# Create an empty set to store the other square's values.
|
|
|
|
other_values = set()
|
|
|
|
|
|
|
|
other_squares = [
|
|
|
|
other_square
|
|
|
|
for other_square in self.squares
|
|
|
|
if other_square.coordinates != square.coordinates
|
|
|
|
and other_square.grid == square.grid
|
|
|
|
]
|
|
|
|
|
|
|
|
for other_square in other_squares:
|
|
|
|
self.__debug(other_square.coordinates)
|
|
|
|
# update other_values with values of other squares
|
|
|
|
other_values = other_values.union(other_square.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" current number: {number}")
|
|
|
|
self.squares[square_nb].values = {str(number)}
|
|
|
|
|
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-11-30 13:55:22 +01:00
|
|
|
def compute_possible_values(self):
|
2019-12-03 12:00:32 +01:00
|
|
|
"""Compute possible values for each square in the board.
|
|
|
|
|
|
|
|
This method should be run just after instantiation to update each
|
2019-12-03 16:02:24 +01:00
|
|
|
square's values, or after the board is updated.
|
2019-12-03 12:00:32 +01:00
|
|
|
"""
|
2019-11-30 13:55:22 +01:00
|
|
|
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug(">>>Compute possible values")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-12-02 17:34:39 +01:00
|
|
|
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}"
|
|
|
|
)
|
2019-12-02 08:51:47 +01:00
|
|
|
|
|
|
|
self.__debug_print_board()
|
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
def compute_obligated_values(self):
|
|
|
|
"""Find obligated values 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-03 12:00:32 +01:00
|
|
|
|
2019-12-03 21:28:37 +01:00
|
|
|
Do the same for each column.
|
|
|
|
|
|
|
|
Do the same for each grid.
|
2019-12-03 12:00:32 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
This method should be run after compute_possible_values to update each
|
|
|
|
square's values.
|
2019-12-01 20:11:17 +01:00
|
|
|
"""
|
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
self.__debug(">>>Compute obligated values")
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_pause()
|
2019-12-01 20:11:17 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
self.__compute_obligated_in_each_line()
|
|
|
|
self.__compute_obligated_in_each_column()
|
|
|
|
self.__compute_obligated_in_each_grid()
|
2019-12-01 20:11:17 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
def get_square(self, line: int, column: int) -> Tuple[Optional[Square],
|
|
|
|
Optional[int]]:
|
|
|
|
"""Given a line and a column, return corresponding square and number.
|
2019-12-01 20:11:17 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
If the square wasn't found, i.e. the coordinates are not valid, return
|
|
|
|
None.
|
|
|
|
"""
|
2019-12-01 19:27:38 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
searched_coordinates: Tuple[int, int] = (line, column)
|
|
|
|
|
|
|
|
for square_nb, square in enumerate(self.squares):
|
|
|
|
if square.coordinates == searched_coordinates:
|
|
|
|
return square, square_nb
|
|
|
|
|
|
|
|
return None, None
|
2019-12-02 08:51:47 +01:00
|
|
|
|
2019-12-03 12:00:32 +01:00
|
|
|
def update_board(self):
|
2019-12-03 16:02:24 +01:00
|
|
|
"""Update the board to reflect self.string's current state.
|
|
|
|
|
|
|
|
This method should be run after updating the board's string. Otherwise
|
|
|
|
it is just like resetting the board to its instantiation state.
|
|
|
|
"""
|
2019-11-30 17:04:43 +01:00
|
|
|
|
2019-12-03 12:00:32 +01:00
|
|
|
self.__debug(">>>Update board")
|
|
|
|
self.__debug_pause()
|
|
|
|
|
|
|
|
self.lines = self.__compute_line_values()
|
|
|
|
self.columns = self.__compute_column_values()
|
|
|
|
self.grids = self.__compute_grid_values()
|
|
|
|
self.squares = self.__compute_square_values()
|
2019-11-30 17:52:45 +01:00
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
def update_string(self) -> str:
|
2019-12-03 12:00:32 +01:00
|
|
|
"""Update self.string to reflect the board's current state."""
|
|
|
|
|
|
|
|
self.__debug(">>>Update board.string")
|
2019-12-02 08:51:47 +01:00
|
|
|
self.__debug_pause()
|
|
|
|
|
2019-12-03 12:00:32 +01:00
|
|
|
output: str = ""
|
|
|
|
|
2019-11-30 17:52:45 +01:00
|
|
|
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
|
|
|
|
|
2019-12-03 16:02:24 +01:00
|
|
|
if self.string == output and '.' in self.string:
|
|
|
|
raise StopIteration("No update was made, will loop forever.")
|
|
|
|
|
2019-12-03 12:00:32 +01:00
|
|
|
self.string = output
|
2019-12-03 16:02:24 +01:00
|
|
|
|
|
|
|
return output
|