алгоритм продолжаеть размешать фигуру даже если он и так разместил: import tkinter as tk
from tkinter import messagebox
import copy

# Размеры сеток
MAIN_GRID_SIZE = 9      # Размер основной сетки
BLOCK_GRID_SIZE = 5     # Размер блока
CELL_SIZE = 30          # Размер ячеек в пикселях

# Основная сетка с инициализацией всех ячеек как свободных (0)
MAIN_GRID = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 1, 1],
[1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0]
]

# Определение трёх блоков с различными формами
BLOCKS = [
    [],
    [],
    []
]

class BlockudokuSolver:
    def __init__(self, main_grid, blocks, update_callback, score_callback):
        self.main_grid = main_grid
        self.blocks = copy.deepcopy(blocks)  # Список типов блоков
        self.update_callback = update_callback  # Функция для обновления поля
        self.score_callback = score_callback    # Функция для обновления счета
        self.score = 0

    def next_step(self):
        """
        Пытаемся разместить один блок за шаг.
        Возвращает True, если блок размещён, иначе False.
        """
        for block in self.blocks:
            for y in range(MAIN_GRID_SIZE):
                for x in range(MAIN_GRID_SIZE):
                    if self.can_place(block, x, y):
                        self.place_block(block, x, y)
                        self.update_callback()      # Обновляем отображение после размещения блока
                        self.clear_filled()        # Проверяем и очищаем заполненные области
                        return True                 # Разместили блок, шаг успешен
        return False                            # Не удалось разместить ни один блок

    def can_place(self, block, x_offset, y_offset):
        """
        Проверяет возможность размещения блока в заданной позиции.
        """
        for x, y in block:
            x_new = x + x_offset
            y_new = y + y_offset
            if x_new < 0 or y_new < 0 or x_new >= MAIN_GRID_SIZE or y_new >= MAIN_GRID_SIZE:
                return False
            if self.main_grid[y_new][x_new] != 0:
                return False
        return True

    def place_block(self, block, x_offset, y_offset):
        """
        Размещает блок на основной сетке.
        """
        for x, y in block:
            self.main_grid[y + y_offset][x + x_offset] = 1

    def clear_filled(self):
        """
        Очищает заполненные строки, столбцы и блоки 3x3.
        Начисляет очки за каждую очищенную область.
        """
        filled_rows = [y for y in range(MAIN_GRID_SIZE) if all(self.main_grid[y][x] == 1 for x in range(MAIN_GRID_SIZE))]
        filled_cols = [x for x in range(MAIN_GRID_SIZE) if all(self.main_grid[y][x] == 1 for y in range(MAIN_GRID_SIZE))]
        filled_blocks = []

        # Проверка блоков 3x3
        for block_row in range(3):
            for block_col in range(3):
                block_filled = True
                for y in range(block_row * 3, block_row * 3 + 3):
                    for x in range(block_col * 3, block_col * 3 + 3):
                        if self.main_grid[y][x] != 1:
                            block_filled = False
                            break
                    if not block_filled:
                        break
                if block_filled:
                    filled_blocks.append((block_row, block_col))

        # Очистка заполненных строк
        for y in filled_rows:
            for x in range(MAIN_GRID_SIZE):
                self.main_grid[y][x] = 0
            self.score += 10  # Добавляем очки за заполнение строки

        # Очистка заполненных столбцов
        for x in filled_cols:
            for y in range(MAIN_GRID_SIZE):
                self.main_grid[y][x] = 0
            self.score += 10  # Добавляем очки за заполнение столбца

        # Очистка заполненных блоков 3x3
        for block in filled_blocks:
            block_row, block_col = block
            for y in range(block_row * 3, block_row * 3 + 3):
                for x in range(block_col * 3, block_col * 3 + 3):
                    self.main_grid[y][x] = 0
            self.score += 30  # Добавляем очки за заполнение блока 3x3

        if filled_rows or filled_cols or filled_blocks:
            # Обновляем счет
            self.score_callback(self.score)
            # Обновляем отображение поля
            self.update_callback()
            # Опционально: отображаем сообщение (можно убрать для автоматического решения)
            # messagebox.showinfo(
            #     "Очистка",
            #     f"Очистка: {len(filled_rows)} строк(а), {len(filled_cols)} столбцов(а), {len(filled_blocks)} блока(ов) 3x3.\nОчки: {self.score}"
            # )

class BlockudokuApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Blockudoku Solver")

        # Основная сетка
        self.main_grid = copy.deepcopy(MAIN_GRID)
        self.main_canvas = tk.Canvas(
            root,
            width=MAIN_GRID_SIZE * CELL_SIZE,
            height=MAIN_GRID_SIZE * CELL_SIZE,
            bg="white"
        )
        self.main_canvas.grid(row=0, column=0, padx=10, pady=10)
        self.draw_grid(self.main_canvas, MAIN_GRID_SIZE, MAIN_GRID_SIZE)
        self.update_main_canvas()

        # Нижняя панель для отображения блоков
        self.blocks = copy.deepcopy(BLOCKS)  # Используем полный список блоков
        self.block_canvases = []
        for i in range(len(self.blocks)):
            frame = tk.Frame(root)
            frame.grid(row=1, column=i, padx=5, pady=5)
            label = tk.Label(frame, text=f"Блок {i+1}")
            label.pack()
            canvas = tk.Canvas(
                frame,
                width=BLOCK_GRID_SIZE * CELL_SIZE,
                height=BLOCK_GRID_SIZE * CELL_SIZE,
                bg="lightgrey"
            )
            canvas.pack()
            self.draw_grid(canvas, BLOCK_GRID_SIZE, BLOCK_GRID_SIZE)
            # Передача текущего индекса блока в lambda через аргумент по умолчанию
            canvas.bind("<Button-1>", lambda event, idx=i: self.toggle_block_cell(idx, event))
            self.block_canvases.append(canvas)
            self.redraw_block(i)  # Рисуем исходное состояние блока

        # Отображение счета
        self.score = 0
        self.score_label = tk.Label(root, text=f"Очки: {self.score}")
        self.score_label.grid(row=2, column=0, columnspan=len(self.blocks), pady=5)

        # Кнопки управления
        control_frame = tk.Frame(root)
        control_frame.grid(row=3, column=0, columnspan=len(self.blocks), pady=10)

        self.step_button = tk.Button(control_frame, text="Следующий шаг", command=self.step)
        self.step_button.pack(side=tk.LEFT, padx=5)

        self.auto_solve_button = tk.Button(control_frame, text="Авто-решение", command=self.auto_solve)
        self.auto_solve_button.pack(side=tk.LEFT, padx=5)

        self.reset_button = tk.Button(control_frame, text="Сброс", command=self.reset_game)
        self.reset_button.pack(side=tk.LEFT, padx=5)

        # Стартовое состояние для алгоритма
        self.solver = None

    def draw_grid(self, canvas, rows, cols):
        """Рисует сетку на заданном канвасе."""
        for i in range(rows + 1):
            canvas.create_line(0, i * CELL_SIZE, cols * CELL_SIZE, i * CELL_SIZE)
        for j in range(cols + 1):
            canvas.create_line(j * CELL_SIZE, 0, j * CELL_SIZE, rows * CELL_SIZE)

    def toggle_block_cell(self, block_idx, event):
        """
        Изменяет состояние клетки в блоке при клике.
        Позволяет пользователю редактировать форму блоков.
        """
        x = event.x // CELL_SIZE
        y = event.y // CELL_SIZE
        if 0 <= x < BLOCK_GRID_SIZE and 0 <= y < BLOCK_GRID_SIZE:
            coords = self.blocks[block_idx]
            if (x, y) in coords:
                coords.remove((x, y))  # Убираем клетку, если она была занята
            else:
                coords.append((x, y))  # Добавляем клетку в блок
            self.redraw_block(block_idx)

    def redraw_block(self, block_idx):
        """Перерисовывает указанный блок."""
        canvas = self.block_canvases[block_idx]
        canvas.delete("all")
        self.draw_grid(canvas, BLOCK_GRID_SIZE, BLOCK_GRID_SIZE)
        coords = self.blocks[block_idx]
        for x, y in coords:
            canvas.create_rectangle(
                x * CELL_SIZE, y * CELL_SIZE,
                (x + 1) * CELL_SIZE, (y + 1) * CELL_SIZE,
                fill="black",
                outline=""
            )

    def start_solver(self):
        """Инициализирует экземпляр BlockudokuSolver."""
        blocks_coords = self.get_blocks_coordinates()
        if not blocks_coords:
            messagebox.showwarning("Предупреждение", "Нарисуйте хотя бы один блок.")
            return False
        self.solver = BlockudokuSolver(
            self.main_grid,
            blocks_coords,
            self.update_main_canvas,
            self.update_score
        )
        return True

    def step(self):
        """Выполняет один шаг алгоритма размещения блока."""
        if not self.solver:
            started = self.start_solver()
            if not started:
                return

        success = self.solver.next_step()
        if success:
            self.update_main_canvas()
            self.update_score()
        else:
            messagebox.showinfo("Конец решения", "Алгоритм разместил все возможные блоки.")
            self.step_button.config(state="disabled")
            self.auto_solve_button.config(state="disabled")

    def auto_solve(self):
        """Автоматически выполняет шаги алгоритма размещения блоков."""
        if not self.solver:
            started = self.start_solver()
            if not started:
                return
        success = self.solver.next_step()
        if success:
            self.update_main_canvas()
            self.update_score()
            self.root.after(100, self.auto_solve)  # Повторить через 100 мс
        else:
            messagebox.showinfo("Конец решения", "Алгоритм разместил все возможные блоки.")
            self.auto_solve_button.config(state="disabled")
            self.step_button.config(state="disabled")

    def reset_game(self):
        """Сбрасывает игру до начального состояния."""
        self.main_grid = copy.deepcopy(MAIN_GRID)
        self.update_main_canvas()
        self.score = 0
        self.score_label.config(text=f"Очки: {self.score}")
        self.solver = None
        self.step_button.config(state="normal")
        self.auto_solve_button.config(state="normal")

    def update_main_canvas(self):
        """Обновляет отображение основной сетки."""
        self.main_canvas.delete("all")
        self.draw_grid(self.main_canvas, MAIN_GRID_SIZE, MAIN_GRID_SIZE)
        for y in range(MAIN_GRID_SIZE):
            for x in range(MAIN_GRID_SIZE):
                if self.main_grid[y][x]:
                    self.main_canvas.create_rectangle(
                        x * CELL_SIZE, y * CELL_SIZE,
                        (x + 1) * CELL_SIZE, (y + 1) * CELL_SIZE,
                        fill="black"
                    )

    def update_score(self, new_score):
        """Обновляет отображение счета."""
        self.score = new_score
        self.score_label.config(text=f"Очки: {self.score}")

    def get_blocks_coordinates(self):
        """Получает координаты всех активированных блоков."""
        blocks_coords = []
        for block in self.blocks:
            # Удаляем дублирующиеся координаты и сортируем
            unique_block = sorted(set(block))
            if unique_block:
                blocks_coords.append(unique_block)
        return blocks_coords

if __name__ == "__main__":
    root = tk.Tk()
    app = BlockudokuApp(root)
    root.mainloop()