Example #1
0
 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)
Example #2
0
 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])
Example #3
0
 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 
Example #4
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)
Example #5
0
 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
Example #7
0
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)
Example #8
0
 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)
Example #9
0
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
Example #10
0
    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 __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 
Example #12
0
 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]])
Example #13
0
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
Example #14
0
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)
Example #15
0
    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])
Example #16
0
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')
Example #17
0
 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)
Example #19
0
 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)
Example #20
0
 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))
Example #22
0
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()

Example #23
0
 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)
Example #24
0
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))
Example #25
0
 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])
Example #26
0

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()
Example #27
0
    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]
Example #29
0
 def _create_pat32(self):
     f = Nonogram()
     f.set_left([[2], [1], [1]])
     f.set_top([[2], [1, 1]])
     return f
Example #30
0
 def test_repr_left(self):
     f = Nonogram()
     f.set_left([[1],[1,2]])
     result = f._repr_left()
     self.assertEqual(result, ["  1","1 2"])
Example #31
0
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
Example #32
0
 def test_repf_top(self):
     f = Nonogram()
     f.set_top([[1],[3,2]])
     result = f._repr_top()
     self.assertEqual(result, ["    3"," 1  2"])
Example #33
0
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]]
Example #34
0
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]