def test_line_len(self): f = Nonogram() l = f._line_len([3]) self.assertEqual(l, 3) l = f._line_len([1,2]) self.assertEqual(l, 4) l = f._line_len([1,2,3,4]) self.assertEqual(l, 13)
def test_line2field(self): f = Nonogram() fld = f._line2field([3]) self.assertEqual(fld, [Nonogram.BLACK]*3) fld = f._line2field([1,1]) self.assertEqual(fld, [Nonogram.BLACK,Nonogram.WHITE,Nonogram.BLACK]) fld = f._line2field([1,2,1]) self.assertEqual(fld, [Nonogram.BLACK,Nonogram.WHITE,Nonogram.BLACK, Nonogram.BLACK,Nonogram.WHITE,Nonogram.BLACK])
def __init__(self): self.nonogram = Nonogram() self.rows = self.nonogram.get_row_constraints() self.col = self.nonogram.get_column_constraints() self.row_length = len(self.rows) self.col_length = len(self.col) # define a state self.state = State(self.rows, self.col) # get permutations from nonogram.py (attempt 1) self.col_permutations = self.hash_col_permutations() self.row_permutations = self.hash_row_permutations() self.traversed = 0 self.all_created_nodes = 0
def test_line_match(self): n = Nonogram() prof = [1] data = [[[UN,UN], [UN,UN]], [[UN,BK], [WT,BK]], [[BK,UN], [BK,WT]], [[UN,WT], [BK,WT]], [[WT,UN], [WT,BK]] ] while data: (f, expect) = data.pop(0) result = n._line_match(f, prof) self.assertEqual(result, expect)
def init_games(self): try: loaded_data = ast.literal_eval(self.window['-LOADED_DATA-'].get()) if not isinstance(loaded_data, list): raise ValueError() self.games = [] for id in loaded_data: if not isinstance(id, int): raise ValueError() nonogram = Nonogram(self.games_queue) nonogram.load_from_db(id) self.games.append(nonogram) except (ValueError, SyntaxError): sg.popup_error('Entered data incorrect') self.window['-NO_OF_GAMES-'].update(str(len(self.games)))
def initialize_cells(my_image): width = my_image.shape[1] * cell_width height = my_image.shape[0] * cell_width nonogram = Nonogram(width, height, cell_width) for i in range(cell_width, height + cell_width, cell_width): for j in range(cell_width, width + cell_width, cell_width): if my_image[(i - cell_width) // cell_width, (j - cell_width) // cell_width] == 0: color = BLACK else: color = WHITE cell = Cell(((j - cell_width) // cell_width, (i - cell_width) // cell_width), j, i, color, cell_width) nonogram.add_cell(cell) return nonogram, width, height
def save_nonogram_from_url(url): try: nono_id, nono_name, d = parse_nonogram(url) except Exception as e: # print('Ссылка не туда') print(e) else: columns_number = d[1][0] % d[1][3] + d[1][1] % d[1][3] - d[1][2] % d[ 1][3] # ширина rows_number = d[2][0] % d[2][3] + d[2][1] % d[2][3] - d[2][2] % d[2][ 3] # высота colors_number = d[3][0] % d[3][3] + d[3][1] % d[3][3] - d[3][2] % d[3][ 3] nono_answer = np.full((rows_number, columns_number), 0) # print('Columns number =', columns_number) # print('Rows number', rows_number) v = colors_number + 5 black_rows_number = d[v][0] % d[v][3] * ( d[v][0] % d[v][3]) + d[v][1] % d[v][3] * 2 + d[v][2] % d[v][3] decoder = d[v + 1] for x in range(v + 2, v + 1 + black_rows_number + 1): for v in range(d[x][0] - decoder[0] - 1, d[x][0] - decoder[0] + d[x][1] - decoder[1] - 1): nono_answer[d[x][3] - decoder[3] - 1][v] = d[x][2] - decoder[2] header_rows = get_header_from_array(nono_answer) header_columns = get_header_from_array(nono_answer.T) header = Header(header_rows, header_columns) nonogram = Nonogram(nono_id, nono_name, header, nono_answer) nono_db.write_nonogram_to_db(nonogram)
def test_check_update(self): f = Nonogram() f._check_update(Nonogram.UNKNOWN, Nonogram.WHITE) f._check_update(Nonogram.UNKNOWN, Nonogram.BLACK) with self.assertRaises(ValueError): f._check_update(Nonogram.WHITE, Nonogram.BLACK) with self.assertRaises(ValueError): f._check_update(Nonogram.BLACK, Nonogram.WHITE)
def compare_blocks_cols(nonogram: Nonogram): diff_sum = 0 for col in range(NUM_COLS): for row in range(NUM_ROWS): clue = nonogram.get_col_clue(col, row) if clue == -1: raise Exception( "Indexes are off in compare_blocks_cols! row: %d col: %d matrix size: (%d, %d)" % (row, col, len(nonogram.matrix), len(nonogram.matrix[0]))) diff_sum += math.fabs(clue - nonogram.matrix[row][col]) return diff_sum
def __init__(self, gui_queue, filename=None, db_id=None): self.main_gui_queue = gui_queue if filename is not None or db_id is not None: self.game = Nonogram() if filename is not None: self.game.load_from_file(filename) else: self.game.load_from_db(db_id) else: raise ValueError("GUIGame object wasn't initialized!") self.game_width, self.game_height = 0, 0 self.BOX_WIDTH, self.BOX_HEIGHT = 0, 0 # window sizes related variables self.WINDOW_SIZE_X = 500 # initial width of the window self.WINDOW_SIZE_Y = 500 # initial height of the window self.stored_size = (0, 0) # width and height of the last drawn window self.reload_size = True # boolean for checking if the window needs to be redrawn due to window resizing self.TIP_SIZE = 150 # widht (for rows) or height (for columns) of box containing hints # variables storing object ids of hints/board self.row_hint_ids = [ ] # array storing IDs of drawn objects of hints for rows self.col_hint_ids = [ ] # array storing IDs of drawn objects of hints for columns # init display variables self.layout = None # layout of current window self.window = None # pointer to window object self.puzzle = None # pointer to window fragment containing puzzle tiles self.row_hints = None # pointer to window fragment containing hints for rows self.col_hints = None # pointer to window fragment containing hints for columns self.queue = queue.Queue() self.threads_id = []
def test_solve(self): f = self._create_pat32() f.solve() self.assertEqual(f._field, [[1,1], [1,0], [0,1]]) f = Nonogram() f.set_left([[2], [1, 1]]) f.set_top([[2], [1], [1]]) f.solve() self.assertEqual(f._field, [[1,1,0], [1,0,1]])
def test_init_overlap(): rows = [[1, 1], [5], [5], [3], [1]] cols = [[2], [4], [4], [4], [2]] nono = Nonogram(rows, cols) e = nono.EMPTY u = nono.UNKNOWN f = nono.FILLED answer = [[u, u, u, u, u], [f, f, f, f, f], [f, f, f, f, f], [u, f, f, f, u], [u, u, u, u, u]] s = Solver(nono) s.initial_overlaps() assert nono.board == answer
def random_nonogram(row_count, col_count): cell_count = row_count * col_count # nonograms with half of the cells colored are the hardest colored_cell_count = np.random.binomial(cell_count, 0.5) fields = list(itertools.product(range(row_count), range(col_count))) colored_fields = random.sample(fields, k=colored_cell_count) nonogram = Nonogram(row_count, col_count, colored_cells=colored_fields) nonogram.calculate_descriptors() nonogram.reset_cells() if nonogram.solve(): return nonogram else: return random_nonogram(row_count, col_count)
def test_slide_line(self): WT = Nonogram.WHITE BK = Nonogram.BLACK UN = Nonogram.UNKNOWN f = Nonogram() fld = f._slide_line([2], 2) self.assertEqual(fld, [BK, BK]) fld = f._slide_line([2], 3) self.assertEqual(fld, [UN, BK, UN]) fld = f._slide_line([2], 4) self.assertEqual(fld, [UN]*4) fld = f._slide_line([3,1], 5) self.assertEqual(fld, [BK,BK,BK,WT,BK]) fld = f._slide_line([3,1], 6) self.assertEqual(fld, [UN,BK,BK,UN,UN,UN]) fld = f._slide_line([3,1], 7) self.assertEqual(fld, [UN,UN,BK,UN,UN,UN,UN])
import tkinter as tk from tkinter import messagebox, Button from nonogram import Nonogram from image import Img import numpy as np # ##### DEFINE GRID HERE ###### # ROWS = 10 COLS = 10 # Visual size of grid box GRID_SIZE = 40 # Initialize nonogram = Nonogram() img = Img() tiles = [[0 for _ in range(COLS)] for _ in range(ROWS)] def create_grid(event=None): w = grid.winfo_width() # Get current width of canvas h = grid.winfo_height() # Get current height of canvas grid.delete('grid_line') # Will only remove the grid_line # Creates all vertical lines at intevals of 100 for i in range(0, w, GRID_SIZE): grid.create_line([(i, 0), (i, h)], tag='grid_line') # Creates all horizontal lines at intevals of 100 for i in range(0, h, GRID_SIZE): grid.create_line([(0, i), (w, i)], tag='grid_line')
def test_check_total(self): f = Nonogram() f.set_left([[1],[0]]) f.set_top([[1]]) msg = f._check_total() self.assertEqual(msg, "") f.set_left([[2],[1],[1]]) f.set_top([[2],[1,1]]) msg = f._check_total() self.assertEqual(msg, "") f.set_left([[1],[0]]) f.set_top([[0]]) msg = f._check_total() self.assertEqual(msg, "not match total left:top=1:0")
from config import NUM_COLS, NUM_ROWS, pickle_unsolved_file_path from nonogram import Nonogram import pickle csv_path = '../data/hanjie.csv' save = True # True -> save to file, False -> load file # saves the Nonograms in csv_path to a pickled list of unsolved Nonograms with open(csv_path) as csvfile: readCSV = csv.reader(csvfile, delimiter=',') filtered = [ { 'title': row[2], 'number': int(row[3]), 'solution': row[4], 'rows': row[6], 'cols': row[7] } for i, row in enumerate(readCSV) if i > 0 and int(row[0]) == NUM_COLS and int(row[1]) == NUM_ROWS ] nonograms = [ Nonogram(d['rows'], d['cols'], d['title'], d['number'], d['solution']) for d in filtered ] for nono in nonograms: print(nono) with open('../' + pickle_unsolved_file_path, 'wb') as f: pickle.dump(nonograms, f)
def test_set_top(self): f = Nonogram() self.assertEqual(f.len_column(), 0) f.set_top([[0],[0],[0]]) self.assertEqual(f.len_column(), 3)
def test_set_left(self): f = Nonogram() self.assertEqual(f.len_row(), 0) f.set_left([[0],[0]]) self.assertEqual(f.len_row(), 2)
five_strip1 = [( 1, 2, ) + 6 * (1, ), (2, ) + 6 * (1, ) + (2, ), (1, ), (2, 1, 1, 2, 1, 2, 1), (1, 1, 1, 2, 1, 1, 1)] three_strip = [(1, ), (18, ), (1, 1)] five_strip2 = [(1, 2, 1, 2, 1, 1, 2), (1, 1, 1, 2, 1, 1, 1), (1, ), (2, 1, 1, 2, 1, 2, 1), (1, 1, 1, 2, 1, 1, 1)] two_strip = [(1, 2, 1, 2, 1, 1, 2), (1, 1, 1, 2, 1, 1, 1)] #r_row = five_strip1 + three_strip + five_strip2 + three_strip + two_strip #r_col = [(3,2,3,2,1),(1,1,2,1,1,2,1)] + [(1,)*7 for _ in range(15)] + [(2,1,3,1,3)] ## small but constraint propagation alone won't solve #r_row = [(4,),(4,),(1,),(1,1),(1,)] # not possible to solve with this solver #r_col = [(1,),(2,1),(2,1),(2,1),(1,1)] # not possible to solve with this solver puzzle = Nonogram(r_row, r_col) ## set solution #puzzle.set_grid(solution) ## solve game start_time = time.time() grid = solve(puzzle) puzzle.set_grid(grid) end_time = time.time() puzzle.show_grid(show_instructions=True, to_file=False, symbols="x#.?") print(puzzle.is_complete(), "{:.2f}%%".format(puzzle.progress * 100)) print("time taken: {:.5f}s".format(end_time - start_time)) print("solved with {} guesses".format(puzzle.guesses))
from nonogram import Square, Nonogram col_num = [[2], [1, 1], [1, 2], [1, 1], [2]] row_num = [[1], [1, 1], [1, 1], [1, 1, 1], [1]] n = Nonogram(row_num, col_num) n.print_nonogram()
def test_is_fit(self): n = Nonogram() self.assertEqual(n._is_fit([UN,WT,BK,UN], [WT,WT,BK,WT]), True) self.assertEqual(n._is_fit([UN,WT,UN], [WT,BK,WT]), False) self.assertEqual(n._is_fit([UN,BK,UN], [WT,WT,WT]), False)
class GeneticAlgorithm(object): global file file = open('output.txt', 'w') # instance of nonogram board global nonogram, population, fitness, ROWS, COLUMNS, ROW_COUNT, COLUMN_COUNT, POPSIZE, MUTATIONPROB, CROSSOVERPROB nonogram = Nonogram() population = [] fitness = [] ROWS = nonogram.get_row_constraints() COLUMNS = nonogram.get_column_constraints() ROW_COUNT = len(ROWS) COLUMN_COUNT = len(COLUMNS) POPSIZE = 300 MUTATIONPROB = None CROSSOVERPROB = 75 # generate random population of suitable solutions for nonogram def generate_population(self): pop = [] for i in range(0, POPSIZE): state = nonogram.get_random_state(ROWS, COLUMNS) # get solution generated based on row constraints pop.append(state[0]) return pop # evaluate the fintness of each solution in the population def evaluate_fitness(self, pop): fit = [] # print pop for sol in pop: fit.append(nonogram.check_all_col(sol)) return fit '''1ST APPROACH''' # create a new population by repeating # selection, crossover, mutation, accepting def create_new_population_1(self, pairs): pop = [] # create a new population by repeating # selection, crossover, mutation, accepting (placing new offspring in new pop) parents = self.selection(pairs) while len(pop) <= POPSIZE: cross = self.crossover(parents) offspring = self.mutation(cross) # place new offspring in a new population pop.append(offspring) print("PARENTS ARE\n") print nonogram.print_state(parents[0]) print("\n\n") print nonogram.print_state(parents[1]) print("\n\n") # return new generated population return pop '''2ND APPROACH''' # create a new population by repeating # selection, crossover, mutation, accepting def create_new_population_2(self, pairs): pre_pop = [] # create a new population by repeating # selection, crossover, mutation, accepting (placing new offspring in new pop) # select best individuals based on their fitness chosen = self.selection_2(pairs) chosen = sorted(chosen, key=operator.attrgetter('fitness')) pre_pop.append(chosen[0].get_state()) pre_pop.append(chosen[1].get_state()) # the rest crossover cross = self.crossover_2(chosen[2:]) pre_pop.extend(cross) # print pre_pop # mutate the whole pre_pop post_pop = [] for i in range(0, len(pre_pop)): offspring = self.mutation(pre_pop[i]) # place new offspring in a new population post_pop.append(offspring) # '''WRITING TO FILE''' # file.write("BEST 2 ARE\n") # file.write(nonogram.print_state(chosen[0].get_state())) # file.write(str(chosen[0].get_fit())) # file.write("\n\n") # file.write(nonogram.print_state(chosen[1].get_state())) # file.write(str(chosen[1].get_fit())) # file.write("\n\n") print("BEST 2 ARE\n") print nonogram.print_state(chosen[0].get_state()) print str(chosen[0].get_fit()) print("\n\n") print nonogram.print_state(chosen[1].get_state()) print(str(chosen[1].get_fit())) print("\n\n") # return new generated population return post_pop # use roulette wheel to select best solutions from a population according to their fitness # the better fitness, the bigger chance to be selected def selection_2(self, pairs): sum_fit = 0 # store fitness in an array fit_array = [] for i in pairs: sum_fit += i.get_fit() fit_array.append(i.get_fit()) # print "sum fit is", sum_fit fit_array.reverse() chosen = [] fitness_function = [] index = 0 for j in fit_array: fit_val = j # print "fit val is", fit_val fitness_function.append(range(index, index + fit_val)) index += fit_val # print fitness_function for k in range(0, POPSIZE): probability = random.randint(0, sum_fit) for a in range(0, len(fitness_function)): if probability in fitness_function[a]: chosen.append(pairs[a]) # print len(chosen) # print chosen return chosen # with a crossover probability cross over the parents to form 2 new offsprings # return a list of new offsprings def crossover_2(self, parents): index_i = 0 index_j = 1 # list of offsprings to return list = [] while index_i < len(parents) and index_j < len(parents): # generate a random probability probability = random.randint(0, 100) if probability < CROSSOVERPROB: # generate a random crossover point crossover_point = random.randint(0, ROW_COUNT) # copy everything before this point from parent 1 and after this point from parent 2 offspring1 = [] offspring2 = [] # get states of parents state1 = parents[index_i].get_state() state2 = parents[index_j].get_state() for i in range(0, crossover_point): offspring1.append(state1[i]) offspring2.append(state2[i]) for j in range(crossover_point, ROW_COUNT): offspring1.append(state2[j]) offspring2.append(state1[j]) list.append(offspring1) list.append(offspring2) else: list.append(parents[index_i].get_state()) list.append(parents[index_j].get_state()) index_i += 2 index_j += 2 return list '''1ST APPROACH''' # select 2 solutions from a population according to their fitness # the better fitness, the bigger chance to be selected def selection(self, pairs): sorted_pairs = sorted(pairs, key=operator.attrgetter('fitness')) sol1 = sorted_pairs[0] sol2 = sorted_pairs[1] # '''WRITING TO FILE''' # file.write("PARENTS ARE\n") # file.write(nonogram.print_state(sol1.get_state())) # file.write(str(sol1.get_fit())) # file.write("\n") # file.write(nonogram.print_state(sol2.get_state())) # file.write(str(sol2.get_fit())) # file.write("\n") # list of 2 chosen solutions chosen = [sol1.get_state(), sol2.get_state()] return chosen # with a crossover probability cross over the parents to form a new offspring def crossover(self, parents): # generate a random probability probability = random.randint(0, 100) if probability < CROSSOVERPROB: # generate a random crossover point crossover_point = random.randint(0, ROW_COUNT) # copy everything before this point from parent 1 and after this point from parent 2 offspring = [] for i in range(0, crossover_point): offspring.append(parents[0][i]) for j in range(crossover_point, ROW_COUNT): offspring.append(parents[1][j]) else: offspring = parents[0] return offspring # with a mutation probability mutate new offspring at each locus (position in chromosome) def mutation(self, offspring): # new mutated offspring new = [] for i in range(0, ROW_COUNT): # 20% chance a row get mutated probability = random.randint(0, 100) if probability <= MUTATIONPROB: # print "row" + str(i) + " get mutated" # print offspring[i] rand = nonogram.get_random_row(i) # print "became" # print rand new.append(rand) else: new.append(offspring[i]) return new # return solution if all constraints are met def check_goal(self, pop): for sol in pop: if nonogram.check_all_col(sol) is 0: return sol return None # method to pair each solution with its fitness def pair_up(self, pop, fitness): pairs = [] for i in range(0, len(pop)): solution = Solution(pop[i], fitness[i]) pairs.append(solution) return pairs # main method def __init__(self): if len(sys.argv) == 1: print "Choose an approach: greedy or proper" print "'python genetic.py greedy' or 'python genetic.py proper'" return if str(sys.argv[1]) == "greedy": global MUTATIONPROB MUTATIONPROB = 30 elif str(sys.argv[1]) == "proper": global MUTATIONPROB MUTATIONPROB = 5 start = time.time() global population, fitness # STEP 1: generate random population population = self.generate_population() # loop to return the best solution in current population flag = None counter = 0 while flag is None: counter += 1 string = "Generation " + str(counter) + "\n" # file.write(string) # STEP 2: evaluate fitness of each sol in population fitness = self.evaluate_fitness(population) # pair each solution with its fitness pairs = self.pair_up(population, fitness) # STEP 3: create a new population if str(sys.argv[1]) == "greedy": population = self.create_new_population_1(pairs) elif str(sys.argv[1]) == "proper": population = self.create_new_population_2(pairs) # STEP 4: check if the end condition is satisfied flag = self.check_goal(population) end = time.time() runtime = end - start '''WRITING TO FILE''' # file.write("SOLUTION IS \n") # file.write(nonogram.print_state(flag)) # file.write("\n") # file.write("\nRUNTIME IS") # file.write(str(runtime)) print "RUNTIME IS %s" % runtime print "Number of generations: %s" % counter print(nonogram.print_state(flag))
def test_line_or(self): n = Nonogram() self.assertEqual(n._line_or([WT,BK,BK],[BK,BK,WT]), [UN,BK,UN]) self.assertEqual(n._line_or([UN,BK,UN],[WT,BK,BK]), [UN,BK,UN])
if __name__ == '__main__': #file_name = "lost.txt" file_name = "beach.txt" #file_name = "artist.txt" # faster with match_forwards than match backwards. NFA is of course the fastest #file_name = "balance.txt" #file_name = "warship.txt" #file_name = "bear.txt" # the webpbn puzzles are super hard #file_name = "webpbn-01611-For merilinnuke" +'.txt' file_name = 'puzzles/' + file_name runs_row, runs_col, solution = decode(file_name) puzzle = Nonogram(runs_row, runs_col) find_solution = True make_guess = True # set solution if solution and not find_solution: # the webpbn files have solutions print("setting solution ...") grid_sol = decode_solution(solution, len(runs_row), len(runs_col)) puzzle.set_grid(grid_sol) ##solve game if find_solution: start_time = time.time() grid = solve_fast(puzzle, make_guess=make_guess) #grid = solve(puzzle) end_time = time.time()
def event_handler(self): event, values = self.window.read(timeout=100) # handle queue from other GUI windows while len(self.queue) > 0: self.read_queue(self.queue[-1]) self.queue.pop(-1) if event in (None, ): return False if event in '-INIT_GAMES-': try: loaded_data = ast.literal_eval( self.window['-LOADED_DATA-'].get()) if not isinstance(loaded_data, list): raise ValueError() self.games = [] for id in loaded_data: if not isinstance(id, int): raise ValueError() nonogram = Nonogram() nonogram.load_from_db(id) self.games.append(nonogram) except (ValueError, SyntaxError): sg.popup_error('Entered data incorrect') if not self.game_gui_opened and event in ('...file', '-FILE_LOAD-'): self.game_gui = GUIGame(self.queue, 'test.txt') self.game_gui.set_layout() self.game_gui_opened = True if not self.game_gui_opened and event in ('...database', '-DB_LOAD-'): popup_text = sg.popup_get_text('Choose puzzle ID (1-9800)', 'Load puzzle from database') if popup_text: puzzle_id = int(popup_text) if 0 < puzzle_id < 9801: self.game_gui = GUIGame(self.queue, db_id=puzzle_id) self.game_gui.set_layout() self.game_gui_opened = True self.game_gui.reload() self.game_gui.redraw_hints(self.game_gui.game.rows, self.game_gui.game.cols) self.game_gui.redraw() if not self.database_gui_opened and event == 'Browse database': self.database_gui = GUIDatabase(self.queue) self.database_gui.set_layout() self.database_gui_opened = True if self.game_gui_opened and not self.game_gui.event_handler(): self.game_gui = None self.game_gui_opened = False if self.database_gui_opened and not self.database_gui.event_handler(): self.database_gui = None self.database_gui_opened = False return True
class Backtracking_Search(): def __init__(self): self.nonogram = Nonogram() self.rows = self.nonogram.get_row_constraints() self.col = self.nonogram.get_column_constraints() self.row_length = len(self.rows) self.col_length = len(self.col) # define a state self.state = State(self.rows, self.col) # get permutations from nonogram.py (attempt 1) self.col_permutations = self.hash_col_permutations() self.row_permutations = self.hash_row_permutations() self.traversed = 0 self.all_created_nodes = 0 # Backtracking Search pseudocode from the textbook # returns None if no solution is found def backtracking_search(self, state): node = Node(state, None, 0) return self.recursive_backtracking(node) def recursive_backtracking(self, node): # check for goal state if self.is_goal(copy.deepcopy(node.state.get_board())): print 'Depth: ', node.depth print 'All created nodes: ', self.all_created_nodes print 'All traversed: ', self.traversed return node # get al possible permutations for the row we're currently trying to fill rows = self.get_row_permutations(node.state.filledIndex) for row in rows: new_state = copy.deepcopy(node.state) new_state.add_row(list(row)) self.all_created_nodes += 1 # as long as this newly added row doesn't violate any constraints if self.check_violations(new_state): self.traversed += 1 new_node = Node(new_state, node, node.depth + 1) result = self.recursive_backtracking(new_node) # recurse if result is not None: return result new_state.remove_row() # remove var from assignment return None # Check constraint violation (2nd/successful attempt) def check_violations(self, state): if(state.filledIndex == len(state.get_board())): if not self.is_goal(copy.deepcopy(state.get_board())): return False board = zip(*(state.get_board())) for i in range(0, len(board)): if not self.check_col_violations(board[i], self.col[i]): return False return True # check a single column def check_col_violations(self, col, constraints): filled = 0 all_filled = 0 # check if there are too many '#' in the column; obviously a violation for c in col: if c == '#': filled += 1 if c != '?': all_filled += 1 if filled > sum(constraints): return False counter = 0 # track block size curr_constraint = 0 # tracks the index of the constraint i = 0 while i < all_filled: if curr_constraint == len(constraints): break if col[i] == '#': if constraints[curr_constraint] > (all_filled - i): return True counter = 1 for j in range(i + 1, all_filled): if col[j] != '#': break counter += 1 if counter != constraints[curr_constraint]: return False curr_constraint += 1 i += counter else: i += 1 return True def is_goal(self, state): new_state = zip(*(state)) for i in range(len(self.col_permutations)): if ''.join(new_state[i]) not in self.col_permutations[i]: return False return True # -------------------- Previous Attempt(s) ----------------------- def must_have_cols(self, col_constraints): # call must_have_rows and transpose the rows to columns return zip(*(self.must_have_rows(col_constraints))) # attempt 1 def must_have_rows(self, row_constraints): solution = [] for constraints in row_constraints: poss_sol = self.nonogram.get_permutations(constraints, len(row_constraints)) row_sol = [True] * len(row_constraints) for sol in poss_sol: for i in range(len(sol)): if sol[i] == '-': row_sol[i] = False solution.append(row_sol) return solution # attempt 1 (unc) def constraint_check(self, board): solution = self.must_have_cols(self.col) for row in range(self.row_length): for col in range(self.col_length): # if the correct sol must have a "#" and it does not (ignore unfilled spots) # we've detected a constraint violation if solution[row][col] and board[row][col] == '-': return False # other obvious checks for c in range(self.col_length): current_col = [] for x in range(self.row_length): current_col.append(board[x][c]) filled_count = 0 for col in current_col: if col == '#': filled_count += 1 # check that there are not more filled blocks than constraints if filled_count > sum(self.col[c]): return False elif filled_count == sum(self.col[c]): # if filled == constraints, check goal # TODO: check if this function works? if self.nonogram.check_constraint_col(board, 0) != 0: return False return True # no constraint violations detected. # Takes the 'must have' row solutions and column solutions and figures out # which parts of the board absolutely must be filled ("#"). # We probably won't need this because this is less restrictive than what we currently have # just for fun, not actually used anywhere. Might end up being useful tho. def get_all_constraints(self): row_sol = self.must_have_rows(self.rows) col_sol = self.must_have_cols(self.col) final_sol = [[False for x in range(self.row_length)] for y in range(self.col_length)] for i in range(self.col_length): for j in range(self.row_length): if row_sol[i][j] and col_sol[i][j]: # if both are true final_sol[i][j] = True return final_sol def hash_col_permutations(self): # map index of column to a list of its constraints col = dict() for c in range(len(self.col)): col[c] = self.nonogram.get_permutations(self.col[c], self.row_length) return col def hash_row_permutations(self): row = dict() for r in range(len(self.rows)): row[r] = self.nonogram.get_permutations(self.rows[r], self.col_length) return row # returns the list of possible permutations at row[index] def get_row_permutations(self, index): return self.row_permutations[index] # returns the list of possible permutations at column[index] def get_col_permutations(self, index): return self.col_permutations[index]
def _create_pat32(self): f = Nonogram() f.set_left([[2], [1], [1]]) f.set_top([[2], [1, 1]]) return f
def test_repr_left(self): f = Nonogram() f.set_left([[1],[1,2]]) result = f._repr_left() self.assertEqual(result, [" 1","1 2"])
class GUIGame: def __init__(self, gui_queue, filename=None, db_id=None): self.main_gui_queue = gui_queue if filename is not None or db_id is not None: self.game = Nonogram() if filename is not None: self.game.load_from_file(filename) else: self.game.load_from_db(db_id) else: raise ValueError("GUIGame object wasn't initialized!") self.game_width, self.game_height = 0, 0 self.BOX_WIDTH, self.BOX_HEIGHT = 0, 0 # window sizes related variables self.WINDOW_SIZE_X = 500 # initial width of the window self.WINDOW_SIZE_Y = 500 # initial height of the window self.stored_size = (0, 0) # width and height of the last drawn window self.reload_size = True # boolean for checking if the window needs to be redrawn due to window resizing self.TIP_SIZE = 150 # widht (for rows) or height (for columns) of box containing hints # variables storing object ids of hints/board self.row_hint_ids = [ ] # array storing IDs of drawn objects of hints for rows self.col_hint_ids = [ ] # array storing IDs of drawn objects of hints for columns # init display variables self.layout = None # layout of current window self.window = None # pointer to window object self.puzzle = None # pointer to window fragment containing puzzle tiles self.row_hints = None # pointer to window fragment containing hints for rows self.col_hints = None # pointer to window fragment containing hints for columns self.queue = queue.Queue() def reload(self): self.clear_hints() self.clear_board() self.game_width = self.game.width self.game_height = self.game.height # Update box sizes according to current size of the diagram self.BOX_WIDTH = self.WINDOW_SIZE_X // self.game_width self.BOX_HEIGHT = self.WINDOW_SIZE_Y // self.game_height # Init array storing ids of board tiles self.board_ids = [[None for _ in range(self.game_width)] for _ in range(self.game_height)] def change_size(self, x, y): # calculate longest array of tips max_row = max([len(r) for r in self.game.rows]) max_col = max([len(c) for c in self.game.cols]) max_tip = max(max_row, max_col) self.TIP_SIZE = max_tip * 5 + 30 self.WINDOW_SIZE_X = int(0.8 * x) - self.TIP_SIZE # - 30 self.WINDOW_SIZE_Y = int(0.8 * y) - self.TIP_SIZE # - 30# - 76 self.reload() self.puzzle.set_size((self.WINDOW_SIZE_X, self.WINDOW_SIZE_Y)) self.BOX_WIDTH = self.WINDOW_SIZE_X // self.game_width self.BOX_HEIGHT = self.WINDOW_SIZE_Y // self.game_height self.row_hints.set_size((self.TIP_SIZE, self.WINDOW_SIZE_Y)) self.col_hints.set_size( (self.TIP_SIZE + self.WINDOW_SIZE_X, self.TIP_SIZE)) self.redraw_hints(self.game.rows, self.game.cols) self.redraw() return self.window.Size def set_layout(self): self.layout = [ [sg.Text('Nonogram'), sg.Text('', key='-OUTPUT-')], [ sg.Graph((self.TIP_SIZE + self.WINDOW_SIZE_X, self.TIP_SIZE), (0, self.TIP_SIZE), (self.TIP_SIZE + self.WINDOW_SIZE_X, 0), key='-COLUMNS-', change_submits=True, drag_submits=False) ], [ sg.Graph((self.TIP_SIZE, self.WINDOW_SIZE_Y), (0, self.WINDOW_SIZE_Y), (self.TIP_SIZE, 0), key='-ROWS-', change_submits=True, drag_submits=False), sg.Graph((self.WINDOW_SIZE_X, self.WINDOW_SIZE_Y), (-1, self.WINDOW_SIZE_Y + 1), (self.WINDOW_SIZE_X + 1, -1), key='-GRAPH-', change_submits=True, drag_submits=False) ], [ sg.Button('Check'), sg.FileBrowse('Load file', target='-FILEBROWSE-'), sg.Button('Load from database'), sg.Button('Solve with DFS') ], [sg.Input(key='-FILEBROWSE-', enable_events=True, visible=False)] ] self.window = sg.Window('Window Title', self.layout, finalize=True, resizable=True) self.puzzle = self.window['-GRAPH-'] self.row_hints = self.window['-ROWS-'] self.col_hints = self.window['-COLUMNS-'] def clear_hints(self): # remove all row hints for ID in self.row_hint_ids: self.row_hints.delete_figure(ID) # remove all columns hints for ID in self.col_hint_ids: self.col_hints.delete_figure(ID) # clear variables storing ids of hints self.row_hint_ids = [] self.col_hint_ids = [] def clear_board(self): for row in range(self.game_height): for col in range(self.game_width): self.puzzle.delete_figure(self.board_ids[row][col]) self.board_ids = [[None for _ in range(self.game_width)] for _ in range(self.game_height)] def update_box(self, x, y): self.puzzle.delete_figure(self.board_ids[x][y]) self.board_ids[x][y] = None self.redraw() def redraw_hints(self, rows, cols): # first remove all existing hints self.clear_hints() max_no_row_hints, max_no_col_hints = max([len(r) for r in rows]), max( [len(c) for c in cols]) # populate all row hints for row_index, row in enumerate(rows): no_of_hints = len(row) for index, num in enumerate(row): rh = self.row_hints.draw_text( '{}'.format(num if num > 0 else ""), (10 + self.TIP_SIZE * (index / len(row)), self.BOX_HEIGHT / 2 + row_index * self.BOX_HEIGHT), text_location=sg.TEXT_LOCATION_CENTER, font=f'Courier {int(10 + 10*(1/max_no_row_hints))}') self.row_hint_ids.append(rh) # populate all column hints for col_index, col in enumerate(cols): no_of_hints = len(col) for index, num in enumerate(col): ch = self.col_hints.draw_text( '{}'.format(num if num > 0 else ""), (5 + self.TIP_SIZE + self.BOX_WIDTH // 2 + col_index * self.BOX_WIDTH, 10 + self.TIP_SIZE * (index / len(col))), text_location=sg.TEXT_LOCATION_CENTER, font=f'Courier {int(10 + 10*(1/max_no_col_hints))}') self.col_hint_ids.append(ch) def redraw(self): for row in range(self.game_height): for col in range(self.game_width): if self.board_ids[row][col] is not None: continue if self.game.board[row][col] == 0: self.board_ids[row][col] = self.puzzle.draw_rectangle( (col * self.BOX_WIDTH, row * self.BOX_HEIGHT), (col * self.BOX_WIDTH + self.BOX_WIDTH, row * self.BOX_HEIGHT + self.BOX_HEIGHT), line_color='black', fill_color='white') elif self.game.board[row][col] == 1: self.board_ids[row][col] = self.puzzle.draw_rectangle( (col * self.BOX_WIDTH, row * self.BOX_HEIGHT), (col * self.BOX_WIDTH + self.BOX_WIDTH, row * self.BOX_HEIGHT + self.BOX_HEIGHT), line_color='black', fill_color='black') else: self.board_ids[row][col] = self.puzzle.draw_rectangle( (col * self.BOX_WIDTH, row * self.BOX_HEIGHT), (col * self.BOX_WIDTH + self.BOX_WIDTH, row * self.BOX_HEIGHT + self.BOX_HEIGHT), line_color='black', fill_color='grey') # draw tile number in the tile # self.game.get_solution_from_file('solution.txt') # self.puzzle.draw_text('{}'.format(self.game.solution[row][col]), # (col * self.BOX_WIDTH + self.BOX_WIDTH // 2, row * self.BOX_HEIGHT + self.BOX_HEIGHT // 2), # text_location=sg.TEXT_LOCATION_CENTER, font='Courier 50') def event_handler(self): event, values = self.window.read(timeout=0) # if bad event or 'Exit' event -> close app if event in (None, 'Exit'): return False # handle resizing window if self.reload_size is True and self.stored_size != self.window.Size: self.reload_size = False self.stored_size = self.change_size(self.window.Size[0], self.window.Size[1]) elif self.reload_size is False: self.reload_size = True self.stored_size = (self.window.Size[0], self.window.Size[1]) # check solution if event in 'Check': if self.game.check_solution(): sg.popup_ok('CORRECT') else: sg.popup_ok('WRONG') # load game from database if event in 'Load from database': popup_text = sg.popup_get_text('Choose puzzle ID (1-9800)', 'Load puzzle from database') if popup_text: puzzle_id = int(popup_text) if 0 < puzzle_id < 9801: self.game.load_from_db(puzzle_id) self.reload() self.redraw_hints(self.game.rows, self.game.cols) self.redraw() # load game from file if event in '-FILEBROWSE-': filename = values['-FILEBROWSE-'] if filename is not '': self.game.load_from_file(filename) self.reload() self.redraw_hints(self.game.rows, self.game.cols) self.redraw() # solve game with DFS algorithm if event in 'Solve with DFS': thread_id = threading.Thread(target=self.game.solve, daemon=True) thread_id.start() if event in '-GRAPH-': mouse = values['-GRAPH-'] if mouse == (None, None): return True box_x = mouse[1] // self.BOX_HEIGHT box_y = mouse[0] // self.BOX_WIDTH # check/uncheck box try: _ = self.game.board[box_x][box_y] except IndexError: return True if self.game.board[box_x][box_y] == 0: self.game.board[box_x][box_y] = 1 self.update_box(box_x, box_y) else: self.game.board[box_x][box_y] = 0 self.update_box(box_x, box_y) self.clear_board() self.redraw() return True
def test_repf_top(self): f = Nonogram() f.set_top([[1],[3,2]]) result = f._repr_top() self.assertEqual(result, [" 3"," 1 2"])
from nonogram import Nonogram from solver import Solver rows = [[1, 1], [5], [5], [3], [1]] cols = [[2], [4], [4], [4], [2]] nono = Nonogram(rows, cols) s = Solver(nono, False) s.solve() print(nono.verify()) print(nono) rows = [[1, 2, 3], [3, 1], [4, 2], [1, 3], [1, 2, 3]] cols = [[1, 3], [2], [3], [3, 1], [1], [1], [1, 2], [2, 2], [1, 2], [1]] nono = Nonogram(rows, cols) s = Solver(nono, False) s.solve() print(nono.verify()) print(nono) rows = [[2, 4, 1, 1], [1, 2, 1], [2, 1, 1, 2, 1], [3, 5, 1], [1, 1, 2, 1, 1], [1, 2, 1, 1, 3], [1, 1, 1, 2, 3], [1, 1, 1, 1, 1, 1, 1], [2, 3, 2, 1, 1], [1, 2, 6], [2, 1, 1, 1, 2, 2], [2, 1, 1, 1, 1, 1], [1, 1, 3, 1, 1, 1], [1, 1, 1, 1, 2, 1], [1, 1, 7, 1]] cols = [[1, 3, 7], [1, 2, 3, 2], [4, 1, 1, 1], [1, 1, 3, 1], [4, 1, 1, 1, 1], [1, 1, 2, 1, 3], [1, 3, 1, 2, 1], [1, 3, 2, 1, 1], [1, 4], [5, 1], [3, 2, 3, 2], [4, 1, 2], [5, 1], [2, 2, 2], [1, 11]]