class GridSolver(object): """ Solves nonogram puzzle based on input provided Example: from gridsolver import GridSolver g = GridSolver({'rows':[[1,2], [4], [3], [1,1]], 'cols':[[4], [2], [3], [2,1]]}) g.solve() print g.grid.show() rows = [[1,2], [4], [3], [1,1]] cols = [[4], [2], [3], [2,1]] X-XX XXXX XXX- X--X """ grid = Canvas() input = {'rows':[[]], 'cols':[[]]} # no zeroes (can ensure this in a method) rows = [[]] cols = [[]] width = 0 height = 0 unknown_char = 'O' dot_char = '-' fill_char = 'X' max_iterations = 10 def __init__(self, input = {'rows':[[]], 'cols':[[]]}, max_iterations=10): self.cols = input['cols'] self.width = len(self.cols) self.rows = input['rows'] self.height = len(self.rows) self.grid = Canvas(self.width, self.height, self.unknown_char) self.input = input self.max_iterations = max_iterations def fill_grid_pixel(self, x, y): self.grid.bitmap[x][y] = self.fill_char def dot_grid_pixel(self, x, y): self.grid.bitmap[x][y] = self.dot_char def is_grid_filled(self, x, y): return self.grid.bitmap[x][y] == self.fill_char def is_grid_dotted(self, x, y): return self.grid.bitmap[x][y] == self.dot_char def is_grid_unknown(self, x, y): return self.grid.bitmap[x][y] == self.unknown_char # = not (self.is_grid_dotted(x, y) or self.is_grid_filled(x, y)) def is_grid_complete(self): return not True in map(lambda col: self.unknown_char in col, self.grid.bitmap) def is_grid_row_complete(self, row_index): # checks if no unknowns in row return not self.unknown_char in self.grid.get_row(row_index) def is_grid_col_complete(self, col_index): # checks if no unknowns in col return not self.unknown_char in self.grid.get_col(col_index) def solve(self, max_iterations=10): self.max_iterations = max_iterations iterations = 0 for n in range(0, self.max_iterations): # print n for col_index in range(self.width): #if not self.is_grid_col_complete(col_index): self.grid.bitmap[col_index] = self.solve_col(col_index) for row_index in range(self.height): #if not self.is_grid_row_complete(row_index): for col_index, pixel in enumerate(self.solve_row(row_index)): if self.is_grid_unknown(col_index, row_index): self.grid.colour_pixel(col_index, row_index, pixel) if self.is_grid_complete(): iterations = n + 1 break # print iterations return self.grid.transform() def solve_row(self, row_index): return self.solve_line(row_index, True) def solve_col(self, col_index): return self.solve_line(col_index, False) def solve_line(self, line_index, is_row_not_col): # check all combos in this line if is_row_not_col: combos = self.calculate_combos(self.rows[line_index], self.width) else: combos = self.calculate_combos(self.cols[line_index], self.height) cross_combo = None # create cross referenced combo for combo in combos: # first loop to ignore combos that do not fit with canvas bad_combo = False for opposing_index, pixel in enumerate(combo): col_index, row_index = col_row_index(line_index, opposing_index, is_row_not_col) if (pixel == self.fill_char and self.is_grid_dotted(col_index, row_index)) or \ (pixel == self.dot_char and self.is_grid_filled(col_index, row_index)): bad_combo = True break # stop checking this combo as it does not fit with existing grid if bad_combo: continue # go to next item cos this one aint no good if cross_combo is None: cross_combo = list(combo) # initialise cross reffed combo to first possible combo else: # cycle each pixel to see if it is different to cross combo for opposing_index, pixel in enumerate(combo): col_index, row_index = col_row_index(line_index, opposing_index, is_row_not_col) # pixels that have more than one variant combo are unknown if cross_combo[opposing_index] != self.unknown_char and cross_combo[opposing_index] != pixel: cross_combo[opposing_index] = self.unknown_char if cross_combo == None: # print "no solvable solns", is_row_not_col return [combos[0]] # todo: raise error else: # print "".join(cross_combo) return cross_combo def calculate_combos(self, line, length): slot_count = len(line) + 1 extra_spaces = length - sum(line) - len(line) + 1 space_dists = generate_distributions(extra_spaces, slot_count) combos = [] for space_dist in space_dists: combo = space_dist[0] * self.dot_char for n in range(len(space_dist) - 1): combo += line[n] * self.fill_char + (space_dist[n + 1] + 1) * self.dot_char combo = combo[:length] # strip off the extra space generated in end slot combos.append(combo) return combos