Вы можете себе сильно упростить жизнь при рисовании сложных объектов если начнете использовать функции.

Допустим я рисую некоторую комбинацию фигур:

from tkinter import *

root = Tk()
root.title("Я приложение =)")

canvas = Canvas(root, width=500, height=200, bg="white")
canvas.pack()

canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)

root.mainloop()

и мне допустим надо нарисовать еще одну такую же комбинацию, но у уже в другом месте.

Я б мог, например, взять и скопировать код, но уже с другими координатами

# старый код
canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)

# а тут повторил код, но сдвинул все на 70px
canvas.create_rectangle(100, 100, 170, 190,  fill="#ffd000", outline="#FF0000", width=5)
canvas.create_oval(130, 130, 160, 160,  fill="#FF0000", outline="", width=5)

и вот уже два одинаковых объекта

и вроде все ок, но если мне надо нарисовать десяток объектов или у объекта сильно много примитивных фигур внутри, то вот этот пересчет становится сильно накладным. Его долго и неудобно считать.

Намного удобнее создать свою собственную функцию в которой будут вызываться команда, делается это так:

from tkinter import *

root = Tk()
root.title("Я приложение =)")

canvas = Canvas(root, width=500, height=200, bg="white")
canvas.pack()

# добавил функцию
def draw_object():
    # внутри вызываются команды
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)


# А ЭТО вообще уберем
# canvas.create_rectangle(100, 100, 170, 190,  fill="#ffd000", outline="#FF0000", width=5)
# canvas.create_oval(130, 130, 160, 160,  fill="#FF0000", outline="", width=5)

root.mainloop()

запустим:

хм, ничего не вывелось. А ну да! Надо ведь вызвать нашу функцию.

# ...

canvas.pack()

def draw_object():
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)


# вызываем нашу функцию, для этого пишем ее имя вместе со скобочками
draw_object()

root.mainloop()

проверяем

О! Красота! =)

А как бы теперь нарисовать второй объект?

Попробуем вызвать функцию два раза:

# ...

def draw_object():
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)


draw_object()
draw_object()  # добавил второй вызов


root.mainloop()

тестим:

так, ничего не поменялось. Но это в принципе логично, так как по сути один объект нарисовался поверх другого.

Нам надо как-то реализовать возможность сдвигать объект.

Для этого нам пригодится возможность добавлять к функции параметры.

Давайте добавим два параметра нашей функции. Сдвиг по оси X и сдвиг по оси Y

# ...
canvas.pack()


# добавил параметры
def draw_object(x, y):
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)


draw_object()
draw_object()


root.mainloop()

теперь кстатит если запустить программу она вообще не сработает:

то есть так как мы добавили два аргумента нашей функции, то вызывая ее надо в эти аргументы что-то передавать

# ...
canvas.pack()


def draw_object(x, y):
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)


# добавил аргументы
draw_object(0, 0)
draw_object(150, 10)


root.mainloop()

запускаем:

все еще ничего не поменялось. Как так?

Дело в том, что несмотря на то что мы добавили два аргумента нашей функции

def draw_object(x, y):
    canvas.create_rectangle(30, 30, 100, 120,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60, 60, 90, 90,  fill="#FF0000", outline="", width=5)

мы их при последующем выводе никак не используем. Давайте задействуем эти самые x и y при выводе.

То есть так как они отвечают за сдвиг по осям X и Y соответственно, значит надо просто добавлять эти x и y к координатам при отрисовке, вот так:

def draw_object(x, y):
    canvas.create_rectangle(30 + x, 30 + y, 100 + x, 120 + y,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60 + x, 60 + y, 90 + x, 90 + y,  fill="#FF0000", outline="", width=5)

попробуем еще раз запустить:

Ура! Сработало! =О

То есть как это работает. Если у нас есть такой код:

def draw_object(x, y):
    canvas.create_rectangle(30 + x, 30 + y, 100 + x, 120 + y,  fill="#ffd000", outline="#FF0000", width=5)
    canvas.create_oval(60 + x, 60 + y, 90 + x, 90 + y,  fill="#FF0000", outline="", width=5)


draw_object(0, 0)
draw_object(150, 10)

то при выполнении программы он превращается в некий аналог такого кода:

# для draw_object(0, 0) получается
canvas.create_rectangle(30 + 0, 30 + 0, 100 + 0, 120 + 0,  fill="#ffd000", outline="#FF0000", width=5)
canvas.create_oval(60 + 0, 60 + 0, 90 + 0, 90 + 0,  fill="#FF0000", outline="", width=5)

# для draw_object(150, 10) выходит:
canvas.create_rectangle(30 + 150, 30 + 10, 100 + 150, 120 + 10,  fill="#ffd000", outline="#FF0000", width=5)
canvas.create_oval(60 + 150, 60 + 10, 90 + 150, 90 + 10,  fill="#FF0000", outline="", width=5)

ну и согласитесь что написать draw_object(0, 0) намного приятнее чем прописывать все команды и все сдвиги вручную.

Ну все, вы подкреплены знаниями, гоу пилить задание =)

Задание

Нарисуйте три кораблика