python-3.xtkintertkinter-canvassudoku

How to check if a cell in Tkinter canvas is present in a list


I am trying to create a simple Sudoku game using tkinter

I am using the sudoku library, it generates the puzzle in the form

puzzle = [[3, None, None, None, 2, None, None, None, None], [None, None, None, None, None, None, 7, None, 6], [2, 6, 7, None, None, None, None, None, None], [None, 9, None, None, None, None, None, 1, None], [None, None, None, None, None, None, None, None, None], [None, None, None, None, 4, None, None, None, None], [1, None, None, None, None, None, None, 9, 2], [None, None, None, None, 6, None, None, 
None, 1], [4, None, None, None, None, 1, None, None, None]]

for example

I am trying to make my code edit the cells that had an initial value of None only, and prevent their editting otherwise.

You can try the full code to grasp what do I mean:

from sudoku import Sudoku
from tkinter import *



root = Tk()
root.title('Sudoku')
root.geometry('378x378')
root.resizable(0, 0)



canvas = Canvas(root)
canvas.pack(fill=BOTH, expand=True)



def draw_board():
    canvas.create_line(42, 0, 42, 378, width=2, fill='lightgray')
    canvas.create_line(84, 0, 84, 378, width=2, fill='lightgray')
    
    canvas.create_line(168, 0, 168, 378, width=2, fill='lightgray')
    canvas.create_line(210, 0, 210, 378, width=2, fill='lightgray')
    
    canvas.create_line(294, 0, 294, 378, width=2, fill='lightgray')
    canvas.create_line(336, 0, 336, 378, width=2, fill='lightgray')

    canvas.create_line(0, 42, 378, 42, width=2, fill='lightgray')
    canvas.create_line(0, 84, 378, 84, width=2, fill='lightgray')

    canvas.create_line(0, 168, 378, 168, width=2, fill='lightgray')
    canvas.create_line(0, 210, 378, 210, width=2, fill='lightgray')

    canvas.create_line(0, 294, 378, 294, width=2, fill='lightgray')
    canvas.create_line(0, 336, 378, 336, width=2, fill='lightgray')

    canvas.create_line(126, 0, 126, 378, width=4, fill='black')
    canvas.create_line(252, 0, 252, 378, width=4, fill='black')
    canvas.create_line(0, 126, 378, 126, width=4, fill='black')
    canvas.create_line(0, 252, 378, 252, width=4, fill='black')



puzzle_cells = []

def draw_nums():
    global x_pos, y_pos, puzzle, cell_values
    for y_cord, row in enumerate(puzzle):
        for x_cord, num in enumerate(row):
            x_pos = 20 + x_cord * 42
            y_pos = 20 + y_cord * 42
            cell = chr(ord('a') + x_cord) + str(y_cord + 1)
            if num is None:
                # Only draw numbers for empty cells
                cell_id = canvas.create_text(x_pos, y_pos, text="", fill='gray', font=('Arial', 35, 'bold'), tags=(f"text_{cell}"))
                puzzle_cells.append(cell_id)
            else:
                # Don't allow editing filled cells
                cell_id = canvas.create_text(x_pos, y_pos, text=str(num), fill='gray', font=('Arial', 35, 'bold'), tags=(f"text_{cell}", "uneditable"))
            cell_values[cell] = num

puzzle = Sudoku(3).difficulty(0.8)
puzzle = puzzle.board
x_pos = 20
y_pos = 20
cell_values = {}

def draw_ans(event):
    global x_pos, y_pos, puzzle, cell_values, puzzle_cells
    cell = detect_pos(event)
    if cell:
        cell_id = canvas.find_withtag(f"text_{cell}")
        # Only allow adding numbers to empty cells in the puzzle and not in puzzle_cells
        if cell_values[cell] is None and not cell_id in puzzle_cells:
            new_value = 1  # Start with 1 for empty cells
        else:
            if cell_id not in puzzle_cells:
                new_value = (cell_values[cell] % 9) + 1
        update_cell(cell, new_value)

def update_cell(cell, value):
    global x_pos, y_pos, puzzle, cell_values
    x_cord, y_cord = ord(cell[0]) - ord('a'), int(cell[1]) - 1
    puzzle[y_cord][x_cord] = value
    x_pos = 20 + x_cord * 42
    y_pos = 20 + y_cord * 42
    canvas.delete(f"text_{cell}")  # Delete the old text
    canvas.create_text(x_pos, y_pos, text=str(value), fill='gray', font=('Arial', 35, 'bold'), tags=f"text_{cell}")
    cell_values[cell] = value

def detect_pos(event):
    x_cord, y_cord = event.x // 42, event.y // 42
    if 0 <= x_cord <= 8 and 0 <= y_cord <= 8:
        return chr(ord('a') + x_cord) + str(y_cord + 1)

canvas.bind("<Motion>", detect_pos)
canvas.bind("<Button-1>", draw_ans)

draw_board()
draw_nums()

root.mainloop()

This edits all the cells, I want the cells that provided by puzzle itself to be uneditable


Solution

  • You should keep the original generated board intact and use it to determine whether the cell is editable or not.

    import copy
    ...
    _puzzle = Sudoku(3).difficulty(0.8)
    # duplicate the board to puzzle
    puzzle = copy.deepcopy(_puzzle.board)
    ...
    # function to get the value of a cell in original board
    def get_puzzle_initial_value(cell):
        col = ord(cell[0]) - ord('a')
        row = int(cell[1]) - 1
        return _puzzle.board[row][col]
    
    def draw_ans(event):
        cell = detect_pos(event)
        num = get_puzzle_initial_value(cell)
        if num is None:
            global x_pos, y_pos, puzzle, cell_values, puzzle_cells
            if cell:
                cell_id = canvas.find_withtag(f"text_{cell}")
                # Only allow adding numbers to empty cells in the puzzle and not in puzzle_cells
                if cell_values[cell] is None and not cell_id in puzzle_cells:
                    new_value = 1  # Start with 1 for empty cells
                else:
                    if cell_id not in puzzle_cells:
                        new_value = (cell_values[cell] % 9) + 1
                update_cell(cell, new_value)
    

    Note also that you don't need to delete the current number and create new number inside update_cell(). Use canvas.itemconfigure() to update the cell instead:

    def update_cell(cell, value):
        global x_pos, y_pos, puzzle, cell_values
        x_cord, y_cord = ord(cell[0]) - ord('a'), int(cell[1]) - 1
        puzzle[y_cord][x_cord] = value
        x_pos = 20 + x_cord * 42
        y_pos = 20 + y_cord * 42
        canvas.itemconfigure(f"text_{cell}", text=str(value))
        cell_values[cell] = value