33. Решаем проблемы с двумерным списком

На прошлом уроке мы успешно погрузились в трудности, связанные с отображением ячеек двумерного списка.
Надеюсь, вы дали себе достаточно времени подумать о проблеме.
На самом деле, для ее решения нужно не так уж и много: убрать информацию о позиции ячейки из самой ячейки и передавать ее (r,c) в метод paint при каждой перерисовке.
Я настоятельно рекомендую попробовать это сделать, если вы еще не решили задачу сами, перед тем, как смотреть готовое решение.
Я решил немного упростить и перерисовывать не каждую ячейку, а все поле.
Для этого создал функцию paint()

Весь код:

from random import randrange as rnd, choice
from tkinter import *
import itertools
 
root = Tk()
root.geometry('800x600')
 
canv = Canvas(root, bg = 'white')
canv.pack(fill = BOTH, expand = 1)


m = 34 # размер ячеек
d = 2 # размер поля вокруг ячейки
nr = 6 # количество строк
nc = 8 # количество столбцов
x0 = m // 2 # отступ от левого края
y0 = m // 2 # отступ от вернего края
colors = ['red','yellow','cyan','green']
 
class cell():
    def __init__(self, r, c): # при создании указываем номер строки и столбца, в который помещаем
        self.n = rnd(10) # значение, с которым будем работать
        self.color = choice(colors) # случайный цвет из списка
 
    def paint(self, r, c):
        x1 = x0 + c * m + d
        # рассчитать координаты левого верхнего угла.
        y1 = y0 + r * m + d
        # координаты правого нижнего угла.
        x2 = x1 + m - 2*d # - r
        y2 = y1 + m - 2*d
        self.id = canv.create_rectangle(x1,y1,x2,y2,fill = self.color) # квадратик ячейки
        # текст в центр ячейки
        x = (x1 + x2) / 2
        y = (y1 + y2) / 2
        self.id_text = canv.create_text(x,y, text = self.n, font = "Arial " + str(m//2)) # текст в квадратике, размер зависит от масштаба
 
a = []
for r in range(nr): # 6 строк
    a.append([]) # создаем пустую строку
    for c in range(nc): # в каждой строке - 10 элементов
        a[r].append(cell(r,c)) # добавляем очередной элемент в строку
        
def paint(): # функция для отрисовки всего списка
    canv.delete(ALL)
    for r in range(nr):
        for c in range(nc):
            a[r].paint(r,c) # поскольку ячейка не знает, где она находится, надо сказать ей, чтобы она могла себя нарисовать

def fill_random(event = 0):
    for r in range(nr):
        for c in range(nc):
            a[r].n = rnd(10)
    paint()


def task(event):
    pass
    
fill_random()
    
canv.bind('<3>', task)    
canv.bind('<1>', fill_random)    

mainloop()

Как вы можете видеть, теперь я не перемещаю изображение ячейки, а перерисовываю заново, предварительно все удалив. Разумеется, перерисовывать по одной ячейке эффективнее, но в данном случае это не играет большой роли. Так же пропал метод kill(), теперь удаление ячейки происходит проще: я просто удаляю ее из списка, а удаление изображения произойдет само собой при очистке и новой прорисовке всего массива.
Функция paint() в таком виде очень похожа на вывод массива в консоль.

Теперь можно вернуться к заданиям предыдущего урока и решить их.

1. Удалить ячейку, по которой щелкнули.
def task(event):
    r = (event.y - y0) // m
    c = (event.x - x0) // m
    a[r].pop(c)
    paint()

Ну вот, снова проблемы.
Не стоит переживать — это нормально. Если бы программы можно было писать без ошибок, то зачем бы нужны были тесты, тестеры, и отладчики (эта тема требует отдельной статьи, но она не в ближайших планах. Ищите в поисковой системе по запросу «отладка Python» или «debug Python»)?

Прежде всего, нужно выяснить источник проблемы. «Out of range» — это выход за пределы списка. Попытка обратиться к номеру, которого не существует. Обычная ситуация, если удалять элементы и пытаться вывести список так, как-будто он прежний.

Нужно поправить функцию paint():

def paint(): # функция для отрисовки всего списка
    canv.delete(ALL)
    for r in range(len(a)): # все строки
        for c in range(len(a[r])): # ячейки в этой строке по номерам
            a[r].paint(r,c) # поскольку ячейка не знает, где она находится, надо сказать ей, чтобы она могла себя нарисовать

Вот теперь все в порядке.
Почти.
При щелчке вне ячеек возникает ошибка, хотя программа успешно продолжает работу. Надо бы добавить проверку r,c после перевода координат щелчка в адрес списка.
if r in range(nr) and c in range(nc) не подходит, потму что при удалении количество элементов уменьшается (из-за чего, кстати, довольно сложно сделать удаление элемента так, чтобы остальные не сдвигались). Можно было бы сделать не проверку, а «защиту» от ошибок (try.. except), но это не очень хорошее решение.
Проверка может выглядеть так:

	if r in range(len(a)) and c in range(len(a[r])):

Думаю, что вставить ее в task вы сможете сами.

2. Удалять строку, по которой щелкнули

Это сделать еще проще (я уже говорил это в прошлом уроке):

def task(event):
	r = (event.y - y0) // m
    if r in range(len(a)):
		a.pop(r)
		paint()

Попробуем другие задачи
Перестановка элементов, столбцов, строк.

3. По щелчку «сдвинуть» ячейку вниз, поменять с нижестоящей ячейкой
def task(event):
    r = (event.y - y0) // m
    c = (event.x - x0) // m
    if r in range(len(a)) and c in range(len(a[r])):
        a[r+1], a[r] = a[r], a[r+1]
        paint()

Небольшую проблему в виде ошибки, возникающей при щелчке на нижней строке, предлагаю устранить самостоятельно.

4. Строку, по которой щелкнули, «спустить вниз»: поменять со строкой ниже
def task(event):
    r = (event.y - y0) // m
    if r in range(len(a)):
        a[r+1], a[r] = a[r], a[r+1]
        paint()

5. Столбец, по которому щелкнули, переставить правее
Со столбцом сложнее, придется проходить циклом:

def task(event):
    #r = (event.y - y0) // m
    c = (event.x - x0) // m
    #if r in range(len(a)) and c in range(len(a[r])):
    for r in range(len(a)):
        if r in range(len(a)) and c in range(len(a[r])):
            a[r], a[r] = a[r], a[r]
    paint()

6. Менять строки по двум щечкам: первый показывает, какую строку менять, второй — с какой строкой.
7. Менять ячейки по двум щечкам: первый показывает, какую ячейку менять, второй — с какой ячейкой.

Удаление от элемента до конца/начала строки/столбца
8. По щелчку удалить все ячейки от выделенной, до конца строки
9. По щелчку удалить все ячейки от выделенной, до конца столбца
10. По щелчку удалить все ячейки от выделенной до начала строки

11. По щелчку закрашивать ячейку серым и убирать значение, по второму щелчку — восстанавливать
12. По первому щелчку спрятать все. Далее щелчок открывает ячейку
13. Игра «найди пару»: оставлять пару открытой, только если совпадает цвет и значение
14. По щелчку «раскидывать» значение элемента по соседним (делить на 4). Если их значение превысит 20, то их тоже «раскидывать»

15. Обнулить все ячейки того же цвета, как и та, по которой щелкнули

16. Все закрасить серым, а те, которые больше всех соседей — оранжевым, а те, которые меньше всех соседей — синим
17. Раскрасить все клетки в зависимости от значения. Чем ближе к максимальному — тем краснее, чем ближе к минимальному — тем синее.

18. Сдвинуть по кругу, сдвинуть по строке, сдвинуть по столбцу, сдвинуть по змейке, по диагонали, сдвинуть по кольцу (в обе стороны, 12 задач)

19. Уменьшить значения всех ячеек в два раза

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *