Sudoku = [ ["*",  3 ,  9 , "*", "*", "*", "*", "*",  7 ], 
           ["*", "*", "*",  7 , "*", "*",  4 ,  9 ,  2 ],
           ["*", "*", "*", "*",  6 ,  5 , "*",  8 ,  3 ],
           ["*", "*", "*",  6 , "*",  3 ,  2 ,  7 , "*"],
           ["*", "*", "*", "*",  4 , "*",  8 , "*", "*"],
           [ 5 ,  6 , "*", "*", "*", "*", "*", "*", "*"],
           ["*", "*",  5 ,  2 , "*",  9 , "*", "*",  1 ],
           ["*",  2 ,  1 , "*", "*", "*", "*",  4 , "*"],
           [ 7 , "*", "*", "*", "*", "*",  5 , "*", "*"]
         ]

"""
Explanation: Sudoku
The sudoku we want to solve is shown below:
    <img src="sudoku.png">
This sudoku can be represented by the following list of lists:
End of explanation
"""


def all_different(S):
    return { f'{x} != {y}' for x in S for y in S if x < y }

all_different({'a', 'b', 'c'})

def specific_constraints(Puzzle):
    Constraints = set()
    row = 0
    for Row in Puzzle:
        row += 1
        col  = 0
        for number in Row:
            col += 1
            if number != '*':
                Constraints.add(f'V{row}{col} == {number}')
    return Constraints

specific_constraints(Sudoku)

def sudoku_csp(Puzzle): 
    R = range(1, 9+1)
    S = range(2+1)
    Variables = { f'V{c}{r}' for c in R for r in R }
    Values = set(R)
    Constraints = specific_constraints(Puzzle)
    for row in R:
        Constraints |= all_different({f'V{row}{col}' for col in R})
    for col in R:
        Constraints |= all_different({f'V{row}{col}' for row in R})
    for rb in S:
        for cb in S:
            Constraints |= all_different({f'V{3*rb+r+1}{3*cb+c+1}' for r in S for c in S})
    return Variables, Values, Constraints

sudoku_csp(Sudoku)

"""
Explanation: The function sudoku_csp(Puzzle) takes a given sudoku Puzzle as its argument and returns a CSP that encodes the given sudoku as a CSP.  The variables should have names like $\texttt{V}ij$ where $i,j \in {1,\cdots,9}$.  For example, V21 would be the variable describing the first cell in the second row.
End of explanation
"""


def find_alternative(csp, Solution):
    Variables, Values, Constraints = csp
    formula = ' or '.join({ f'{var} != {Solution[var]}' for var in Variables })
    return Variables, Values, Constraints | { formula }

"""
Explanation: Given a sudoku Puzzle and a Solution for this puzzle, the function find_alternative computes a CSP
that is solvable iff the puzzle has a second solution that is different from Solution.
End of explanation
"""


import ipycanvas as cnv

size = 100

"""
Explanation: Code to Display the Solution
End of explanation
"""


def show_solution(Solution):
    canvas = cnv.Canvas(size=(size * 9, size * 9))
    canvas.font = '20px sans-serif'
    canvas.text_align    = 'center'
    canvas.text_baseline = 'middle'
    for row in range(9):
        for col in range(9):
            x = col * size
            y = row * size
            canvas.line_width = 1.0
            canvas.stroke_rect(x, y, size, size)
            entry = Sudoku[row][col]
            if entry == '*':
                key = f'V{row+1}{col+1}'
                symbol = str(Solution[key])
                canvas.fill_style = 'blue'
            else:
                symbol = str(entry)
                canvas.fill_style = 'black'
            x += size // 2
            y += size // 2
            canvas.fill_text(symbol, x, y)
    canvas.line_width = 3.0
    for row in range(3):
        for col in range(3):
            x = 3 * col * size
            y = 3 * row * size
            canvas.stroke_rect(x, y, 3 * size, 3 * size)
    canvas.stroke_style = 'black'
    canvas.line_width = 6.0
    canvas.stroke_rect(0, 0, 9 * size, 9 * size) 
    display(canvas)

"""
Explanation: The function show_solution prints the solution.
End of explanation
"""


