0

Clouds puzzles have the following rules: Place some clouds into the grid. Clouds are in the shape of rectangles and squares, and at least two squares wide and two squares long. The clouds cannot touch each other, not even diagonally. The numbers outside the grid indicate the total number of cells covered by clouds in the corresponding direction.

I have created a script that places rectangles into an 8x8 grid that do not touch anywhere. However, some of these puzzles have multiple solutions:

grid = [ [1., 1., 0., 0., 0., 0., 0., 0.],
       [1., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 1., 0., 0.],
       [0., 0., 0., 0., 1., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0.] ]
sumsofrows = [2,2,0,0,0,2,2,0]
sumsofcolumns = [2,2,0,0,2,2,0,0]

So I tried to create a backtracking script that checks if a certain puzzle has multiple solutions or not. I based my script on this thread. However, it does not work. (EDIT: The script always returns "0", even though there should be at least 1 solution) Step 1-4 of my script work fine, but step 5 and 6 do not. Any help would be appreciated!

import numpy

#array with three numbers instead of two: 0 = unsolved position, 1 = no cloud, 2 = cloud
gridthree = numpy.copy(grid)
for a in range(8):
    for b in range(8):
        gridthree[a][b] = gridthree[a][b] + 1

# STEP 1: clouds cannot form angles, they must be rectangles
# Any 2x2 grid cannot have exactly 3 pieces of cloud. The fourth cell must also contain cloud
def fill_angles(matrix):
    dummy = 0
    for a in range(7):
        for b in range(7):
            c = matrix[a][b] + matrix[a+1][b] + matrix[a][b+1] + matrix[a+1][b+1]
            if c == 3:
                dummy = 1
                matrix[a][b] = 1
                matrix[a+1][b] = 1
                matrix[a+1][b+1] = 1
                matrix[a][b+1] = 1
    if dummy == 1:
        fill_angles(matrix)     #recursive
    return matrix

#STEP 2
#the same function as in step 1, but for matrices with numbers 0, 1 and 2
def fill_anglesthree(matrixthree):
    valid = True
    for a in range(7):
        for b in range(7):
            if matrixthree[a][b] == 2 and matrixthree[a+1][b] == 2 and matrixthree[a][b+1] == 2 and matrixthree[a+1][b+1] != 2:
                if matrixthree[a+1][b+1] == 0:
                    matrixthree[a+1][b+1] = 2
                    fill_anglesthree(matrixthree)   #recursive
                if matrixthree[a+1][b+1] == 1: #no cloud is allowed here
                    valid = False
            elif matrixthree[a][b] == 2 and matrixthree[a+1][b] == 2 and matrixthree[a][b+1] != 2 and matrixthree[a+1][b+1] == 2:
                if matrixthree[a][b+1] == 0:
                    matrixthree[a][b+1] = 2
                    fill_anglesthree(matrixthree)   #recursive
                if matrixthree[a][b+1] == 1:
                    valid = False
            elif matrixthree[a][b] == 2 and matrixthree[a+1][b] != 2 and matrixthree[a][b+1] == 2 and matrixthree[a+1][b+1] == 2:
                if matrixthree[a+1][b] == 0: 
                    matrixthree[a+1][b] = 2
                    fill_anglesthree(matrixthree)   #recursive
                if matrixthree[a+1][b] == 1:
                    valid = False
            elif matrixthree[a][b] != 2 and matrixthree[a+1][b] == 2 and matrixthree[a][b+1] == 2 and matrixthree[a+1][b+1] == 2:
                if matrixthree[a][b] == 0:
                    matrixthree[a][b] = 2
                    fill_anglesthree(matrixthree)   #recursive
                if matrixthree[a][b] == 1:
                    valid = False
    return matrixthree, valid

#STEP 3: the solved function
#checks if a binary matrix is solved
def solved(matrix):
    # 1 check sum of rows
    sumsofrowsmatrix = []
    for i in range(len(matrix)):
        sum = 0;
        for j in range(len(matrix[0])):
            sum = sum + matrix[i][j]
        sumsofrowsmatrix.append(sum)
    if numpy.array_equiv(sumsofrowsmatrix,sumsofrows) == False:
        return False

    # 2 check sum of columns
    sumsofcolumnsmatrix = []
    for i in range(len(matrix[0])):
        sum = 0;
        for j in range(len(matrix)):
            sum = sum + matrix[j][i]
        sumsofcolumnsmatrix.append(sum)
    if numpy.array_equiv(sumsofcolumnsmatrix,sumsofcolumns) == False:
        return False
    # 3 check if the clouds form angles
    for a in range(len(matrix[0])-1):
        for b in range(len(matrix[0])-1):
            c = matrix[a][b] + matrix[a+1][b] + matrix[a][b+1] + matrix[a+1][b+1]
            if c == 3:
                return False

    return True

#STEP 4: the validity function
#checks if a matrix is still solved if you add i in (row,column)
def validity(matrixthree,i,row,column):
    matrixthree[row][column] = i #i is equal to 1 (no cloud) or 2 (cloud)
    # step 1: angles are filled up
    matrixthree, valid = fill_anglesthree(matrixthree)
    if valid == False:
        return False
    # step 2: create binary matrix
    matrix = numpy.copy(matrixthree)
    for row in range(8):
        for column in range(8):
            if matrix[row][column] != 0:
                matrix[row][column] = matrix[row][column] - 1
    # step 3: check sum of rows
    sumsofrowsmatrix = []
    for i in range(len(matrix)):
        sum = 0;
        for j in range(len(matrix[0])):
            sum = sum + matrix[i][j]
        sumsofrowsmatrix.append(sum)
    for i in range(8):
        if sumsofrowsmatrix[i] > sumsofrows[i]:
            return False

    # step 4: check sum of columns
    sumsofcolumnsmatrix = []
    for i in range(len(matrix[0])):
        sum = 0;
        for j in range(len(matrix)):
            sum = sum + matrix[j][i]
        sumsofcolumnsmatrix.append(sum)
    for i in range(8):
        if sumsofcolumnsmatrix[i] > sumsofcolumns[i]:
            return False
    # step 5: if grid is completely filled but solution is incorrect, then invalid
    dummy = 0                   #dummy becomes 1 if there are any zeros
    for a in range(8):
        for b in range(8):
            if matrixthree[a][b] == 0:
                dummy = 1
    if dummy == 0:  #if there are no zeros left
        if solved(matrix) == False:
            return False
    return True

#STEP 5: find the next empty (0) spot in a matrix
def find_empty(matrixthree):
    for i in range(len(matrixthree)):
        for j in range(len(matrixthree[0])):
            if matrixthree[i][j] == 0:
                return (i, j)

    return None

#STEP 6: solved function 
def solve(matrixthree):
    matrix = numpy.copy(matrixthree)
    for row in range(8):
        for column in range(8):
            if matrix[row][column] != 0:
                matrix[row][column] = matrix[row][column] - 1        
    #check if the variable count already exists
    count_exists = 'count' in locals() or 'count' in globals()
    if count_exists == False:
        count = 0
    
    if find_empty(matrixthree) == None and solved(matrix) == True:
        count = count + 1
        return count
    elif find_empty(matrixthree) != None:
        row, column = find_empty(matrixthree)
    
    for num in [2,1]:    #only numbers 1 and 2 can be filled in
        **original = numpy.copy(matrixthree)**
        if validity(matrixthree,num,row,column) == True:
            matrixthree[row][column] = num
            count = count + solve(matrixthree) #solve recursively
        matrixthree[row][column] = 0  #if no solutions were found, set back to 0 and go back
        else:
            matrixthree = numpy.copy(original)
    return count

matrixthree = numpy.zeros(shape=(8,8))
count = solve(matrixthree)
print(count)

EDIT: The problem is that this algorithm finds one solution and then stops.

Koen
  • 1
  • 2
  • 1
    Can you include the results of your debugging efforts? Moreover, "does not work" is not a good problem statement. Be specific. – trincot Sep 02 '23 at 15:56
  • What is the first statement you found during your debugging session that didn't do what you expected? Where in your code should it find the exepected solution, and where you have noticed that it didn't get there? What was the state of the variables at that moment? Are they what you expected? If so, debug why that state is not recognised as a solution. If not, debug where the first statement is where the variables get a wrong value. Etc. etc. Show your debugging efforts. – trincot Sep 02 '23 at 16:29
  • When I debug, the first two rows go well, but then starting row 3, it goes wrong. It happens in this line: "if validity(matrixthree,num,row,column) == True:". The validity is False, yet somehow matrixthree changes anyway. I don't know what to do about that. Matrixthree is not an output of the function validity, so it shouldn't change matrixthree. – Koen Sep 02 '23 at 18:28
  • I changed my code and now I consistently find one solution. Unfortunately, my script is unable to find a second solution. – Koen Sep 02 '23 at 19:34

0 Answers0