def build_sectors(self): for i in range(N): for j in range(N): sector = ExclusiveSet("sector_{},{}".format(j, i)) for y in range(N * i, N * i + N): for x in range(N * j, N * j + N): sector.add_square(self.grid[y][x]) self.sets.add(sector)
def build_rows(self): for y in range(N_2): row = ExclusiveSet("row_{}".format(y)) for x in range(N_2): row.add_square(self.grid[y][x]) self.sets.add(row)
def build_columns(self): for x in range(N_2): column = ExclusiveSet("col_{}".format(x)) for y in range(N_2): column.add_square(self.grid[y][x]) self.sets.add(column)
class SudokuBoard(object): def __init__(self, x_regions=False, meta_regions=False): self.start_time = time.clock() self.grid = [[Square(x, y) for x in range(N_2)] for y in range(N_2)] self.sets = set() self._log = [] self.cursor_x = 0 self.cursor_y = 0 self.go_forward = True self.x_regions = x_regions self.meta_regions = meta_regions self.original_state = None self.clues = 0 self.saved_states = [] self.redo_states = [] self.build_rows() self.build_columns() self.build_sectors() self.build_x_regions() self.build_meta_regions() def load_game(self, line): """ line is an N_4-character representation of the board where given values are 1-9 and spaces are . If the first character is 'x', enable x-regions. If the first character is 'm', enable meta-regions. Optionally with another N_4 characters representing a solution or partial solution. Optionally after that, another 3*N_4 characters representing a bitmask of 1-shifted possible values as a 3-digit decimal. For instance, a square with possible values 1, 3, and 9 would be 2**(1-1) + 2**(3-1) + 2**(9-1) = 261 """ if not line: line = '.' * N_4 if not self.original_state: self.original_state = line if 'x' in line: self.set_x_regions(True) line = line.translate(None, 'x') else: self.set_x_regions(False) if 'm' in line: self.set_meta_regions(True) line = line.translate(None, 'm') else: self.set_meta_regions(False) if len(line) not in (N_4, 2 * N_4, 5 * N_4): self.log("Invalid line: {} ({} ch)".format(line, len(line))) raise RuntimeError( "Lines (excluding preceding extra region chars) " "must be one of length {}".format((N_4, 2 * N_4, 5 * N_4))) self.clues = 0 for y in range(N_2): for x in range(N_2): char = line[y * N_2 + x] sq = self.grid[y][x] if char != ".": val = int(char) sq.set_value(val, given=True) self.clues += 1 elif len(line) >= 2 * N_4 and line[N_4 + y * N_2 + x] != '.': val = int(line[N_4 + y * N_2 + x]) self.grid[y][x].set_value(val, given=False) elif len(line) >= 5 * N_4: mask_start = 2 * N_4 + 3 * (y * N_2 + x) mask_str = line[mask_start:mask_start+3] if mask_str == '...': continue value_mask = int(mask_str) values = set() for i in range(N_2): if 2**i & value_mask == 2**i: values.add(i + 1) sq.set_possible_values(values) else: self.grid[y][x].clear() self.grid[y][x].reset_values_to_attempt() def current_state(self, givens_only=False, include_possibles=True): # return the state of the board as would be loaded line = '' line += 'x' if self.x_regions else '' line += 'm' if self.meta_regions else '' line2 = '' line3 = '' for y in range(N_2): for x in range(N_2): sq = self.grid[y][x] if sq.get_value(): if sq.is_given: line += str(sq.get_value()) else: line += '.' line2 += str(sq.get_value()) else: line += '.' line2 += '.' if sq.is_unknown(): line3 += '...' elif sq.get_value(): line3 += '.' + str(sq.get_value()) + ( 'g' if sq.is_given else '.') else: line3 += '{:03d}'.format(self._possible_value_mask(sq)) if givens_only: return line if not include_possibles: return line + line2 return line + line2 + line3 def _possible_value_mask(self, sq): mask = 0 for i in sq.possible_values: mask += 2**(i - 1) return mask def unsolved_squares(self): for row in self.grid: for sq in row: if not sq.is_solved(): yield sq def log(self, message, replace=False): dt = time.clock() - self.start_time message = '[{:02d}:{:02d}] {}'.format( int(dt / 60), int(dt % 60), str(message)) if replace: self._log[-1] = message else: self._log.append(message) def is_solved(self): for s in self.sets: if not s.is_solved(): return False return True def select_next_square(self): # advance if self.cursor_x == N_2 - 1: self.cursor_y += 1 self.cursor_x = 0 if self.cursor_y == N_2: self.cursor_y = 0 else: self.cursor_x += 1 def select_prev_square(self): # go back if self.cursor_x > 0: self.cursor_x -= 1 elif self.cursor_y > 0: self.cursor_y -= 1 self.cursor_x = N_2 - 1 else: # back at 0,0 self.cursor_x = N_2 - 1 self.cursor_y = N_2 - 1 def build_rows(self): for y in range(N_2): row = ExclusiveSet("row_{}".format(y)) for x in range(N_2): row.add_square(self.grid[y][x]) self.sets.add(row) def build_columns(self): for x in range(N_2): column = ExclusiveSet("col_{}".format(x)) for y in range(N_2): column.add_square(self.grid[y][x]) self.sets.add(column) def build_sectors(self): for i in range(N): for j in range(N): sector = ExclusiveSet("sector_{},{}".format(j, i)) for y in range(N * i, N * i + N): for x in range(N * j, N * j + N): sector.add_square(self.grid[y][x]) self.sets.add(sector) def build_x_regions(self): self.x_down = ExclusiveSet('x_down', enabled=self.x_regions) self.x_up = ExclusiveSet('x_up', enabled=self.x_regions) for x in range(N_2): self.x_down.add_square(self.grid[x][x]) self.x_up.add_square(self.grid[N_2 - 1 - x][x]) self.sets.add(self.x_down) self.sets.add(self.x_up) def set_x_regions(self, enable_x_regions): self.x_regions = enable_x_regions self.x_down.set_enabled(self.x_regions) self.x_up.set_enabled(self.x_regions) def build_meta_regions(self): self.meta_0 = ExclusiveSet('meta_0', enabled=self.meta_regions) self.meta_1 = ExclusiveSet('meta_1', enabled=self.meta_regions) self.meta_2 = ExclusiveSet('meta_2', enabled=self.meta_regions) self.meta_3 = ExclusiveSet('meta_3', enabled=self.meta_regions) for dx in range(N): for dy in range(N): self.meta_0.add_square(self.grid[1+dy][1+dx]) self.meta_1.add_square(self.grid[1+dy][5+dx]) self.meta_2.add_square(self.grid[5+dy][1+dx]) self.meta_3.add_square(self.grid[5+dy][5+dx]) self.sets.add(self.meta_0) self.sets.add(self.meta_1) self.sets.add(self.meta_2) self.sets.add(self.meta_3) def set_meta_regions(self, enable_meta_regions): self.meta_regions = enable_meta_regions self.meta_0.set_enabled(self.meta_regions) self.meta_1.set_enabled(self.meta_regions) self.meta_2.set_enabled(self.meta_regions) self.meta_3.set_enabled(self.meta_regions) @property def selected_square(self): return self.grid[self.cursor_y][self.cursor_x]