«Черепашья графика» при помощи turtle, рисование при помощи алгоритма

Черепашья графика, turtle – принцип организации библиотеки графического вывода, построенный на метафоре Черепахи, воображаемого роботоподобного устройства, которое перемещается по экрану или бумаге и поворачивается в заданных направлениях, при этом оставляя (или, по выбору, не оставляя) за собой нарисованный след заданного цвета и ширины.

Проще: черепашка ползает по экрану и рисует. Мы управляем черепашкой на плоскости при помощи программы.

Начало работы. Движения

В первой строке необходимо добавить:

import turtle

Мы командуем черепашкой простыми словами на английском языке. left, right – поворот налево и направо, forward и backward – движение вперед и назад. В программе каждое действие – вызов функции из модуля turtle. Простая программа:

import turtle

turtle.right(90)
turtle.forward(100)
turtle.left(90)
turtle.backward(100)

Что произошло:

Не похоже на черепашку, это ползающая стрелка! Исправим это:

import turtle
turtle.shape("turtle")
turtle.fd(100)
turtle.exitonclick()


Отлично! Теперь это черепашка, пусть и монохромная. Дополнительно, функция exitonclick() позволяет закрыть окно и завершить выполнение программы кликом мышкой по окну.
А еще можно использовать сокращенные названия функций: fd(100) вместо forward(100), rt вместо right, lt вместо left, bk вместо backward.

Геометрические фигуры

Рисуем простые геометрические фигуры:

Если мы хотим выполнить инструкции n раз, мы пишем их в цикле

for i in range(n):

Далее идут инструкции с отступом в 4 пробела. Код с отступами – тело цикла. Когда цикл завершается, отступы больше не ставятся.

Рисуем квадрат:

import turtle
square = turtle.Turtle()
square.shape("turtle")
for i in range(4):
    square.forward(100)
    square.right(90)
turtle.exitonclick()

Скучно рисовать одинокие фигуры. Поэтому мы приготовились рисовать сразу несколько и теперь создаем отдельный экземпляр класса Turtle для каждой фигуры. Так мы можем менять цвет линии и другие параметры отдельно для каждой фигуры. Потом, когда мы захотим дорисовать или изменить параметры фигуры, у нее будут сохранены старые параметры. Их не надо будет устанавливать заново, как это было бы без отдельных экземпляров класса для каждой фигуры.

Звезда рисуется также:

Самостоятельно:

  1. Нарисуйте пятиконечную звезду (угол поворота 144 градуса).
  2. Квадрат и звезду в одной программе, на одном графическом поле, но с разными экземплярами класса Turtle.
  3. Восьмиконечную звезду (угол поворота 135 градусов).
  4. Фигуру из анимации в начале страницы.

Решения

Изменяем параметры во время движения

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

import turtle

square = turtle.Turtle()
square.shape("turtle")
square.color('red', 'green')
square.begin_fill()
for j in range(3):
    square.left(20)
    for i in range(4):
        square.forward(100)
        square.left(90)

square.end_fill()
turtle.exitonclick()

Мы также добавили:

Больше программирования!

Напишем обобщенную программу рисования выпуклых равносторонних многоугольников. num_sides – количество граней, side_length – длина грани, angle – угол поворота.

import turtle

polygon = turtle.Turtle()
num_sides = 6
side_length = 100
angle = 360.0 / num_sides

for i in range(num_sides):
    polygon.forward(side_length)
    polygon.right(angle)

turtle.exitonclick()

Что будет, если на каждом шаге увеличивать длину пути? В первый день 10 шагов, во второй – 20, далее 30, 40 и так до 200:

import turtle

spiral = turtle.Turtle()
for i in range(20):
    spiral.forward(i * 10)
    spiral.right(144)
turtle.exitonclick()

Координаты на плоскости

Положение на плоскости определяется двумя числами, x и y:

Черепашку в программе можно перемещать функцией goto(x, y). x и y – числа, или переменные. goto(0, 0) переместит черепашку в начало координат.

import turtle

spiral = turtle.Turtle()
for i in range(20):
    spiral.fd(i * 10)
    spiral.rt(144)
    spiral.goto(0,0)
turtle.exitonclick()

Вместо звезды-спирали мы получили 5 линий, расходящихся из точки начала координат.

Круг и точка

Не хватает плавных изгибов? На помощь приходят функции dot() и circle():

import turtle

turtle.title("Turtle Drawing")
circle = turtle.Turtle()
circle.shape("turtle")
circle.pensize(5)
circle.pencolor("cyan")

circle.dot(20)
circle.penup()
circle.goto(0, -100)
circle.pendown()
circle.circle(100)
turtle.exitonclick()

Дополнительно мы:

Самостоятельно:

Решения

Делаем фигуры равновеликими

Площадь квадрата со стороной 100 пикселей – 10 000 квадратных пикселей. Вычислим площади всех фигур со стороной 100 от треугольника до 7-угольника. Формула площади правильного многоугольника содержит тангенс, поэтому «поверим на слово» результату, зависимости количество углов (вершин) – площадь:

Изобразим ее на графике:

Получается, что площадь 7-угольника в 36339.12 / 4330.13 = 8.4 раза больше, чем площадь треугольника! Это очень заметно на рисунке:

Чтобы фигуры стали равновеликими, надо сделать длину грани вместо константы 100 – переменной, которая зависит от количества углов.

Как: приведем все площади к 10000. Для треугольника площадь увеличится на 10000 / 4330.13 = 2.31 раза. Для 7-угольника – уменьшится в 36339.12 / 10000 = 3.63 раз. Значит, стороны должны измениться в 1.52 и 0.52 раз соответственно, то есть, до 152 и 32.7 пикселей (снова «верим на слово»). Эту зависимость можно нащупать «на глаз», в чем и заключалось задание.

Наша программа без труда масштабируется до большего количества фигур:

Программа, в которой вычисляются точные значения:

import turtle
from math import tan, sqrt, pi

def prepare(x, y, color):
    turtle.penup()
    turtle.goto(x, y)
    turtle.pendown()
    turtle.color(color)
    turtle.begin_fill()

def draw_polygon(num_sides, side_length):
    angle = 360.0 / num_sides
    for i in range(num_sides):
        turtle.forward(side_length)
        turtle.right(angle)
    turtle.end_fill()

def calc_s(num_sides, side_length):
    return num_sides * side_length ** 2 / (4 * tan(pi/num_sides))

def calc_side(square):
    return sqrt(4 * square * tan(pi/num_sides) / num_sides)

turtle.hideturtle()
turtle.speed(10)

colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'black', 'yellow', 'pink', 'brown']
xcoords = [0, 150, -150, 150, -150, 270, -270, 270, -270]
ycoords = [0, 150, -150, -150, 150, 270, -270, -270, 270]

squares = []
numsides = []
for i in range(9):
    num_sides = i + 3
    square = round(calc_s(num_sides, 100), 2)
    side_length = round(calc_side(10000), 3)
    squares.append(square)
    numsides.append(num_sides)
    print("Углов:", num_sides, "была площадь:", square, "стала длина грани:", side_length,
          "изменение в", round(side_length/100, 2), "раз")
    prepare(xcoords[i], ycoords[i], colors[i])
    draw_polygon(num_sides, side_length)

turtle.exitonclick()
print("Список количество углов:", numsides, end="")
print("Список площади:", squares)

Текстовый вывод:

Углов: 3 была площадь: 4330.13 стала длина грани: 151.967 изменение в 1.52 раз
Углов: 4 была площадь: 10000.0 стала длина грани: 100.0 изменение в 1.0 раз
Углов: 5 была площадь: 17204.77 стала длина грани: 76.239 изменение в 0.76 раз
Углов: 6 была площадь: 25980.76 стала длина грани: 62.04 изменение в 0.62 раз
Углов: 7 была площадь: 36339.12 стала длина грани: 52.458 изменение в 0.52 раз
Углов: 8 была площадь: 48284.27 стала длина грани: 45.509 изменение в 0.46 раз
Углов: 9 была площадь: 61818.24 стала длина грани: 40.22 изменение в 0.4 раз
Углов: 10 была площадь: 76942.09 стала длина грани: 36.051 изменение в 0.36 раз
Углов: 11 была площадь: 93656.4 стала длина грани: 32.676 изменение в 0.33 раз
Список количество углов: [3, 4, 5, 6, 7, 8, 9, 10, 11]
Список площади: [4330.13, 10000.0, 17204.77, 25980.76, 36339.12, 48284.27, 61818.24, 76942.09, 93656.4]

Как построить график (если кто захочет):

  1. Поставить Matplotlib, набрав в командной строке
    pip install matplotlib
  2. Запустить программу
    import matplotlib.pyplot as plt
    numsides = [3, 4, 5, 6, 7, 8, 9, 10, 11]
    squares = [4330.13, 10000.0, 17204.77, 25980.76, 36339.12, 48284.27, 61818.24, 76942.09, 93656.4]
    plt.plot(numsides, squares, 'or--')
    plt.xlabel('Количество углов')
    plt.ylabel('Площадь')
    plt.show()

Другие полезные функции:

Пример двух рисунков – экземпляров класса Turtle() – на одном полотне

import turtle

turtle.title("Turtle Circles")
circ = turtle.Turtle()
circ.pencolor("purple")
circ.fillcolor("orange")
circ.shape("circle")
circ.pensize(5)
circ.speed(10)
circ.fd(150)
circ.begin_fill()
circ.circle(90)
circ.end_fill()

n = 10
t = turtle.Turtle()
while n <= 50:
    t.circle(n)
    n += 10

turtle.exitonclick()

Что произошло:

  1. Задали название окна,
  2. создали экземпляр класса Turtle под именем circ. Все изменения сохраняются для класса circ;
  3. цвет линии и заполняющий цвет,
  4. форму и размер курсора,
  5. установили 10-ю скорость
  6. продвинулись на 150 пикселей вперед от старта,
  7. начали заполнять фигуру цветом,
  8. нарисовали круг
  9. закончили заполнять цветом,

Затем:

  1. Объявили переменную n и присвоили ей значение 10,
  2. создали новый экземпляр класса Turtle под именем t. У него нет настроек экземпляра класса circ!
  3. В цикле while: пока переменная n меньше или равна 50, рисовать круги радиусом n;
  4. после нарисованного круга увеличить переменную n на 10.
  5. Алгоритм рисования кругов прекратит рисовать круги после 4-го круга.

Итог: функции и классы на примере turtle

Графический интерфейс средствами библиотеки turtle.

Нарисуем прямоугольник и сделаем его кнопкой: при нажатии кнопка исчезает и появляется круг:

import turtle

wndow = turtle.Screen()
wndow.title("Screen & Button")
wndow.setup(500, 500)

btn1 = turtle.Turtle()
btn1.hideturtle()
for i in range(2):
    btn1.fd(80)
    btn1.left(90)
    btn1.fd(30)
    btn1.left(90)
btn1.penup()
btn1.goto(11,7)
btn1.write("Push me", font=("Arial", 12, "normal"))

def btnclick(x, y):
    if 0<x<80 and 0<y<30:
        print("Кнопка нажата!")
        btn1.clear()
        ball = turtle.Turtle()
        turtle.fillcolor("orange")
        turtle.pencolor("purple")
        turtle.shape("circle")

turtle.listen()
turtle.onscreenclick(btnclick, 1)
turtle.done()

Что произошло:

  1. Задали название и размеры (500 на 500 пикселей) окна,
  2. Создали экземпляр класса btn1 и спрятали курсор (черепашку),
  3. Нарисовали прямоугольник 80 на 30;
  4. подняли перо и перешли на координаты (11, 7);
  5. написали Push me шрифтом Arial 12-го размера, нормальное начертание. Попробуйте вместо normal ключевые слова bold (полужирный), italic (наклонный);

Задаем поведение кнопки:

Самостоятельно:

Уточнения

Управляем рисунком с клавиатуры

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

Клавиша задается строкой с ее названием. Например, 'space' – пробел, 'Up' (с заглавной буквы) – стрелка вверх. Клавиши букв задаются заглавными, только если мы хотим нажать именно заглавную (с Shift или Caps Lock).

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

Создадим экземпляр класса Turtle и выведем его координаты:

import turtle

circ = turtle.Turtle()
circ.shape("circle")
circ.color("orange")
circ.penup()
print(circ.xcor(), circ.ycor())

Получили вывод "0.0 0.0". Теперь напишем функцию up(), которая будет запускаться при нажатии стрелки вверх и перемещать наш circ на 10 пикселей вверх:

import turtle

circ = turtle.Turtle()
circ.shape("circle")
circ.color("orange")
circ.penup()

def up():
    y = circ.ycor() + 10
    circ.sety(y)

turtle.listen()
turtle.onkeypress(up, 'Up')
turtle.done()

Очень похоже на нажатие мышкой! Функцию up() можно сократить до одной строчки:

def up(): circ.sety(circ.ycor() + 10)

Будет работать, но функции в одну строчку писать не принято. Для таких случаев используют анонимные функции: у них может вовсе не быть имени. В Python в качестве анонимных функций используются лямбда-выражения, мы их уже использовали для сортировки. Так будет выглядеть лямбда-функция up:

up = lambda: circ.sety(circ.ycor() + 10)

Она используется у нас только в одном месте, внутри функкии turtle.onkeypress(). А почему бы не соединить их вместе? Так будет выглядеть наша программа в сокращенном виде:

import turtle

circ = turtle.Turtle()
circ.shape("circle")
circ.color("orange")
circ.penup()
turtle.listen()
turtle.onkeypress(lambda: circ.sety(circ.ycor() + 10), 'Up')
turtle.done()

Всего 8 строк, и функции действительно не понадобилось имени! Как видим, язык Python дает возможность писать разными стилями, и мы можем выбирать на свой вкус: писать развернуто и красиво (как писал Гавриил Романович Державин) или кратко (как Эрнест Хемингуэй).

Самостоятельно:

Соединяем все вместе

У нас уже есть кнопка с текстом и обработчик клика мышкой. Соединим все в одну программу:

import turtle

wndow = turtle.Screen()
wndow.title("Circle game")
wndow.setup(500, 500)

btn1 = turtle.Turtle()
btn1.hideturtle()
for i in range(2):
    btn1.fd(80)
    btn1.left(90)
    btn1.fd(30)
    btn1.left(90)
btn1.penup()
btn1.goto(4, 5)
btn1.write("Start!", font=("Arial", 12, "normal"))

circ = turtle.Turtle()
circ.hideturtle()
circ.shape("circle")
circ.color("orange")

def btnclick(x, y):
    if 0<x<80 and 0<y<30:
        btn1.clear()
        circ.showturtle()
        circ.penup()

turtle.listen()
turtle.onscreenclick(btnclick, 1)
turtle.onkeypress(lambda: circ.sety(circ.ycor() + 10), 'Up')
turtle.done()

Есть стартовый экран, управляемый с клавиатуры персонаж... Добавим препятствие, и уже почти готова игра!

import turtle

wndow = turtle.Screen()
wndow.title("Circle game")
wndow.setup(500, 500)

btn1 = turtle.Turtle()
btn1.hideturtle()
for i in range(2):
    btn1.fd(80)
    btn1.left(90)
    btn1.fd(30)
    btn1.left(90)
btn1.penup()
btn1.goto(4, 5)
btn1.write("Start!", font=("Arial", 12, "normal"))

circ = turtle.Turtle()
circ.hideturtle()
circ.shape("circle")
circ.color("orange")

sq = turtle.Turtle()
sq.hideturtle()
sq.penup()
sq.setposition(-20, 70)

def btnclick(x, y):
  if 0<x<80 and 0<y<30:
      btn1.clear()
      circ.showturtle()
      circ.penup()
      sq.pendown()
      print("sq position:", sq.xcor(), sq.ycor())
      for i in range(4):
          sq.fd(40)
          sq.rt(90)

def up():
    y = circ.ycor() + 10
    circ.sety(y)
    if -20<circ.xcor()<20 and 30<circ.ycor()<70:
        circ.hideturtle()
        sq.clear()
        circ.write("Game over!", font=("Arial", 12, "bold"))

turtle.listen()
turtle.onscreenclick(btnclick, 1)
turtle.onkeypress(up, 'Up')
turtle.done()

Самостоятельно:

Подсказки

Игра с подсчетом ходов

import turtle

def prepare_fig(fig, x, y):
    fig.hideturtle()
    fig.penup()
    fig.setposition(x, y)
    fig.speed(13)

def draw_square(fig, color, side_length):
    fig.pendown()
    fig.fillcolor(color)
    fig.begin_fill()
    for i in range(4):
        fig.fd(side_length)
        fig.rt(90)
    fig.end_fill()

def message(text, color):
    circ.hideturtle()
    circ.goto(0, 0)
    circ.color(color)
    sq.clear()
    sq2.clear()
    print(moves)
    circ.write(text, font=("Arial", 12, "bold"))

def win_or_die(moves):
    if -20 < circ.xcor() < 40 and 10 < circ.ycor() < 70:
        message(GAME_OVER_MSG + str(moves), 'red')
    if -60 < circ.xcor() < -20 and 50 < circ.ycor() < 90:
        message(WIN_MSG + str(moves), 'green')

def movey(deltay):
    global moves
    y = circ.ycor() + deltay
    circ.sety(y)
    moves += 1
    win_or_die(moves)

def movex(deltax):
    global moves
    x = circ.xcor() + deltax
    circ.setx(x)
    moves += 1
    win_or_die(moves)

wndow = turtle.Screen()
wndow.title("Circle game")
wndow.setup(500, 500)

circ = turtle.Turtle()
circ.penup()
circ.shape("circle")
circ.color("orange")

sq = turtle.Turtle()
prepare_fig(sq, -20, 70)
draw_square(sq, 'red', 60)
sq2 = turtle.Turtle()
prepare_fig(sq2, -60, 90)
draw_square(sq2, 'green', 40)

moves = 0
GAME_OVER_MSG = 'Game over!\nСделано шагов: '
WIN_MSG = 'Победа!\nСделано шагов: '
STEP = 10

turtle.listen()
turtle.onkeypress(lambda: movey(STEP), 'Up')
turtle.onkeypress(lambda: movey(-STEP), 'Down')
turtle.onkeypress(lambda: movex(STEP), 'Right')
turtle.onkeypress(lambda: movex(-STEP), 'Left')
turtle.done()

Всего 61 строка кода!

Добавляем в программу классы.

Ссылки