Одновременное рисование черепахами. Метод ontimer модуля turtle языка Python

Словарь урока

из модуля turtle:
  • teleport ‒ телепорт
  • left ‒ налево
  • forward ‒ вперед
  • shape ‒ форма
  • fillcolor ‒ цвет (color) заливки (filling)
  • begin_fill ‒ начать (begin) заполнение (fill)
  • end_fill ‒ конец (end) заполнения (fill)
  • circle ‒ круг, окружность
  • dot ‒ точка
  • numinput ‒ ввод (input) числа (num, number)
  • Turtle ‒ черепаха
  • speed ‒ скорость
  • hideturtle ‒ скрыть (hide) черепаху (turtle)
  • ontimer ‒ по (on) таймеру (timer), или таймер включен (timer on)
из модуля random:
  • random ‒ случайный
  • randint ‒ случайное (random) целое (integer)
из языка Python:
  • import ‒ импорт
  • from ‒ из (от)
  • def ‒ от define (определять)
  • for ‒ для
  • if ‒ если
  • range ‒ диапазон, ряд
  • global ‒ глобальный

Зачем нам на экране несколько черепах, если они все-равно рисуют по очереди? Чтобы последовательно рисовать, достаточно и одной. Сначала она начертит одну фигуру, потом перейдет в другое место и нарисует там другую. Если надо, перед этим поменяет свои настройки.

Интересней было бы, если бы черепахи рисовали одновременно. Одновременное, или параллельное, выполнение разных участков кода ‒ это многопоточность, которая обеспечивается другими модулями и требует более глубоких знаний программирования. Реализовать настоящую многопоточность средствами самого модуля turtle не получится.

Однако мы можем имитировать одновременность, если заставим черепах рисовать по чуть-чуть, быстро переключаясь между ними. Сделать это можно с помощью цикла.

bob = Turtle()
bob.shape('turtle')
lucy = Turtle()
lucy.color('brown')
side = 70
angle = 30

bob.teleport(100, -100)
lucy.teleport(-200, -100)

for i in range(12):
    bob.forward(50)
    lucy.forward(side)
    bob.left(30)
    lucy.left(angle)
    side = side - 1
    angle = angle + 3

В примере Боб рисует круг, Люси ‒ спираль. Но каждая черепаха за одну итерацию цикла чертит только одну черточку. Переменные side и angle нужны только для Люси, так как аргументы ее команд должны меняться. Выражение side = side - 1 означает, что сначала из старого значения side вычитается единица, после этого полученное новое число присваивается side. То есть переменная меняет свое значение.

Другой способ заставить черепах как бы одновременно работать ‒ это использовать метод ontimer модуля turtle. Но сначала изучим, что он делает сам по себе. Метод выполняет задержку на указанное количество миллисекунд (в 1 секунде 1000 миллисекунд). Код, выполнение которого должно задерживаться, помещается в функцию. Имя функции передается первым аргументом в ontimer.

def pink_circle():
    fillcolor('pink')
    begin_fill()
    circle(100)
    end_fill()

dot(10)
ontimer(pink_circle, 50)
teleport(-120, 50)
circle(50, steps=4)
teleport(200, 50)
circle(50, steps=8)

Можно было бы ожидать, что после того как будет нарисована точка, черепаха подождет 50 миллисекунд и начнет чертить круг, который будет касаться точки. Однако выполнение функции, переданной в ontimer, просто откладывается на 50 миллисекунд. При этом черепаха времени даром не теряет. Она исполняет код ниже вызова ontimer. Когда пройдет 50 миллисекунд, она, независимо от того, какую строчку кода сейчас читает, все бросит и пойдет исполнять код функции.

Задержка в 50 миллисекунд очень небольшая. Поэтому черепаха успевает только выполнить команду teleport после ontimer. В результате получается, что сначала она рисует точку, потом телепортируется, потом рисует круг, потом квадрат.

Если в ontimer вторым аргументом передать число 500, то получится такая картина (у вас может быть по-другому, так как видимо есть зависимость от скорости компьютера):

Здесь задержка достаточно большая. Перед исполнением тела функции черепаха успевает не только начертить квадрат, но и телепортироваться в место расположения восьмиугольника.

Вернемся к нашей основной задаче ‒ сделать так, чтобы две черепахи рисовали одновременно. Для этого поместим вызов ontimer в тело функции, а самому методу передадим эту же функцию в качестве аргумента:

t1 = Turtle()
t1.color('brown')
t2 = Turtle()
t2.color('blue')
t2.speed(1)

def f():
    t1.forward(30)
    t1.left(360/12)
    ontimer(f, 200)

f()

t2.circle(-100)

В коде после настроек двух черепах и определения функции, мы сразу вызываем функцию f, после чего вторая черепаха рисует круг. В теле f действия выполняет первая черепаха. Она чертит одну сторону двенадцатиугольника, и вызывается ontimer с этой же функцией, но отложенной на 200 миллисекунд.

Пока отложенная на потом ждет своего "часа", вызванная из основной части программы функция завершает свою работу. Поток выполнения программы уходит в основную часть и здесь начинает исполнять код ниже вызова функции f. Там находится команда, которая заставляет вторую черепаху рисовать окружность.

Черепаха t2, ни о чем не подозревая, начинает рисовать круг. Но тут вдруг поток выполнения программы получает сигнал, что прошло 200 миллисекунд и надо выполнить код функции, который был ранее отложен. Поток выполнения останавливает t2 и уходит выполнять тело функции. Там опять происходит вызов ontimer. Пока длится задержка, поток выполнения снова дает возможность t2 еще немного порисовать. И так далее.

Поскольку переключение между черепахами происходит достаточно быстро, нам будет казаться, что они рисуют одновременно. На самом деле, по очереди.

Проблема кода выше в том, что первая черепаха никогда не остановится, функция f так и будет вызывать себя из себя с помощью ontimer. Мы можем завершить программу, только закрыв окно.

Чтобы такого не было, надо поместить вызов ontimer в условный оператор. Тогда функция будет вызываться только при соблюдении какого-то условия. Что это может быть за условие, зависит от вашей программы. Это, например, может быть значение координаты, лежащее в определенных пределах. Или мы можем ввести глобальную переменную, значение которой менять в функции. При этом вызов ontimer будет зависеть от оценки значения этой переменной:

n = 1

def f():
    t1.forward(30)
    t1.left(360/12)
    global n
    if n < 12:
        n = n + 1
        ontimer(f, 200)

Переменная n должна быть глобальной (то есть определенной в основной части программы), потому что если ее определить внутри функции, то при каждом вызове этой функции она будет получать новое значение 1. И счетчика из нее уже не получится. Чтобы из тела функции обращаться к глобальной переменной в языке программирования Python используется команда global.

Задания для самостоятельной работы

  1. Расставьте три черепахи по горизонтали экрана. Запросите у пользователя длину стороны. Черепахи должны начать одновременно рисовать фигуры с такой стороной. Первая пусть рисует квадрат, вторая треугольник, третья ‒ шестиугольник.
  2. Разработайте программу, в которой две черепахи попеременно телепортируются в случайные места экрана до тех пор, пока не попадут в определенную зону (внутрь небольшой квадратной области). Оказавшаяся там черепаха исчезает.

Turtle. Программирование на Python для школьников




Все разделы сайта