Перейти к содержанию

Примеры DrawZero

На этой странице собрана галерея из 21 примера, которые поставляются вместе с библиотекой DrawZero. Каждый блок ниже подтягивает код напрямую из каталога src/drawzero/examples, поэтому вы видите ровно те же исходники, что и при установке пакета. Используйте галерею как справочник: переходите от иллюстраций к коду, сверяйтесь с комментариями в исходниках и экспериментируйте у себя в редакторе.

Используйте индекс ниже, чтобы быстро перейти к нужному примеру.

Файл Фокус Ключевые идеи
00_hello_world.py Первый рендеринг на холсте Рисует холст с рамкой, разделяет текст на два языка и запускает цикл run().
01_grid_and_coordinates.py Обзор системы координат Рендерит вспомогательную сетку grid(), затем аннотирует различные примитивы в известных координатах.
02_loops_and_rgb_colors.py Искусство, управляемое циклом Использует цикл for и случайные кортежи RGB для рисования вертикальных полос различной высоты.
03_simple_objects.py Каталог примитивов Демонстрирует каждый примитив рисования (линии, круги, повернутые прямоугольники, многоугольники, выравнивание текста).
04_loops_sin_plot.py Построение математических графиков Строит синусоиду, сшивая короткие отрезки линий вдоль оси X.
05_points.py Помощник Pt как вектор/черепаха Показывает, как класс Pt поддерживает арифметику, движение и вращение в стиле черепахи в циклах.
06_turtle_style.py Рисование многоугольников с помощью Pt Вращает экземпляр Pt для построения правильных многоугольников для сторон от 3 до 10 с использованием операций черепахи.
07_animation_circles.py Минимальный цикл анимации Изменяет положение, радиус и цвет круга с течением времени, вызывая tick() на каждом кадре.
08_animation_traffic_light.py Анимация на основе состояний Повторно использует помощника для рисования светофора и использует sleep() для переключения цветов.
09_animation_rectangles.py Сложная анимация Сочетает математику Pt с логикой вращения/орбиты, прозрачностью и условными эффектами.
10_animation_planets.py Орбитальное движение Вычисляет круговое движение для пары планета/луна и перерисовывает каждый кадр.
11_transparency_and_line_width.py Альфа-смешивание и толщина обводки Демонстрирует различные значения альфа-канала и толщины линий на кругах, прямоугольниках, многоугольниках и эллипсах.
12_images.py Рендеринг изображений Загружает cat.png из папки примеров, демонстрирует масштабирование и альфа-канал при выводе изображений.
13_gradients.py Помощник Gradient Создает несколько шкал Gradient и визуализирует их с помощью сложенных прямоугольников.
14_animation_close_vertex.py Анимация графа близости Перемещает случайные узлы Pt с циклическим движением, рисует линии между соседними парами и использует оверлей FPS.
15_animation_firework.py Система частиц Реализует классы Particle и Firework с обновлениями физики, свечением на основе градиента и логикой очистки.
16_keyboard_and_mouse.py Обработка ввода Использует оси, группы модификаторов, действия при нажатии и координаты в момент щелчка.
17_mouse_tube.py Эффект следа мыши Захватывает mouse.pos на каждом кадре, выращивает концентрические круги с градиентом по мере их старения.
18_game_stars.py Мини-игра 3D-звездное поле Использует датаклассы, случайные звезды и управление WASD/QE для навигации по псевдо-3D полю.
19_game_colors.py Игра на реакцию Отображает слова-цвета в сравнении с фактическими цветами, обрабатывает выбор кнопок мыши со штрафами по времени.
20_game_racing.py Многопользовательская мини-игра Назначает отдельные привязки клавиш для каждой машины, прокручивает предварительно вычисленную дорогу и ведет счет для каждого игрока.
99_errors.py Демонстрация сообщений об ошибках Принудительно вызывает ошибки проверки в режиме EJUDGE_MODE, чтобы продемонстрировать локализованную диагностику, выдаваемую конвертерами.

Начало работы

Эти примеры показывают, как вывести первые примитивы на экран и разобраться с базовым циклом run().

00_hello_world.py

00_hello_world

00_hello_world.py
# Just import everything
# Импортируем всё
from drawzero import *

# Red rectangle with upper left corner at (50, 150) and width = 900, height = 700
# Красный прямоугольник с левым верхнем углом в точке(50, 150), шириной 900 и высотой 700
rect('red', (50, 150), 900, 700)

# Straight orange line from (100, 500) to (900, 500)
# Оранжевая прямая линия из точки (100, 500) в точку (900, 500)
line('orange', (100, 500), (900, 500))

# Centered text
# Центрированный текст
text('green', 'Hello world!', (500, 250), fontsize=72)
text('blue', 'Привет, мир!', (500, 750), fontsize=72)

01_grid_and_coordinates.py

01_grid_and_coordinates

01_grid_and_coordinates.py
from drawzero import *

# Canvas is always 1000×1000
# Размер холста всегда 1000×1000

# Coordinate grid
# Координатная сетка для упрощения рисования
grid()

# (100, 200) -> (600, 800)
line('red', (100, 200), (600, 800))

# ┌─ at (600, 100), width=200, height=300
filled_rect('blue', (600, 100), 200, 300)

# Center at (300, 800), radius = 50
circle('green', (300, 800), 50)

# Center of text at (800, 600)
text('yellow', '(800, 600)', (800, 600))

Использование циклов и цветов

Используйте циклы и случайные значения, чтобы быстро получать выразительные узоры и научиться передавать цвета в именованном и RGB-форматах.

02_loops_and_rgb_colors.py

02_loops_and_rgb_colors

02_loops_and_rgb_colors.py
from drawzero import *
from random import randint, seed

# Fill with color #003366 (Dark midnight blue)
# Заливка цветом #003366 (Dark midnight blue)
fill('#003366')

# Draw a vertical line every 8 "pixels"
# Будем рисовать линию через каждые 8 пунктов
for x in range(0, 1000, 8):
    # Color is defined by 3 components: red, greed and blue. Take them at random
    # Каждый цвет состоит из трёх компонент: красной, зелёной и синей. Выбираем их случайно
    red = randint(0, 90)
    green = 255 - randint(0, 90)
    blue = randint(0, 90)
    random_green_color = (red, green, blue)

    # Take line height at random too.
    # Высота тоже случайная
    height = 200 + randint(0, 80)

    # 1000 — is the bottom of the canvas
    # 1000 самый низ экрана
    line(random_green_color, (x, 1000 - height), (x, 1000))

03_simple_objects.py

03_simple_objects

03_simple_objects.py
from drawzero import *

# All shapes

# Fill with color rgb=(50, 50, 50)
fill((50, 50, 50))

# Grid
grid()
text('white', "grid()", (500, 50), 32, '<.')

# Straight line
line('red', (100, 50), (300, 150))
text('white', "line('red', (100, 50), (300, 150))", (500, 100), 32, '<.')

# Circles
circle('yellow', (300, 200), 100)
filled_circle('brown', (100, 200), 50)
text('white', "circle('yellow', (300, 200), 100)", (500, 200 - 20), 32, '<.')
text('white', "filled_circle('brown', (100, 200), 50)", (500, 200 + 20), 32, '<.')

# Rectangles
rect('violet', (100, 300), 100, 200)
filled_rect('yellow', (250, 350, 50, 100))  # tuple[x,y,w,h] is also OK
filled_rect_rotated('red', (350, 350), 50, 100, 135)
rect_rotated('green', (350, 350), 50, 100, 45)
text('white', "rect('violette', (100, 300), 100, 200)", (500, 400 - 40 - 20), 32, '<.')
text('white', "filled_rect('yellow', (250, 350, 50, 100))", (500, 400 - 20), 32, '<.')
text('white', "filled_rect_rotated('red',(350,350),50,100,135)", (500, 400 + 20), 32, '<.')
text('white', "rect_rotated('green', (350, 350), 50, 100, 45)", (500, 400 + 40 + 20), 32, '<.')

# Polygons
filled_polygon('white', [(100, 600), (200, 640), (140, 560)])  # list oftuples
polygon('white', 430, 550, 374, 626, 285, 597, 285, 502, 374, 473)  # or just flattened
text('white', "filled_polygon('white', [(100, 600),(200,640),(140,560)])", (500, 600 - 40), 24, '<.')
text('white', "polygon('white', 430,550,374,626,285,597,285,502,374,473)", (500, 600), 24, '<.')

# Ellipsis and arcs
ellipse('grey', (100, 650), 200, 100)
arc('grey', (300, 650), 200, 100, start_angle=45, stop_angle=270)
arc('red', (350, 650), 100, 100, start_angle=45, stop_angle=270)
text('white', "ellipse('grey', (100, 650), 200, 100)", (500, 700 - 40), 32, '<.')
text('white', "arc('grey', (300, 650), 200, 100, 45, 270)", (500, 700), 32, '<.')
text('white', "arc('red', (350, 650), 100, 100, 45, 270)", (500, 700 + 40), 32, '<.')

# Text
text('red', 'Hello, world!', (100, 800), 48, '<.')
text('white', "text('red', 'Hello, world!', (100, 800), 48, '<.')", (500, 800), 32, '<.')

text('magenta', 'text', (200, 900), 48, '>v')
text('red', '×', (200, 900), 72, '..')
text('magenta', 'align', (200, 900), 48, '<^')
text('white', "text(C.magenta, 'text', (200, 900), 48, '>v')", (500, 860), 32, '<.')
text('white', "text(C.red, '×', (200, 900), 72, '..')", (500, 900), 32, '<.')
text('white', "text(C.magenta, 'align', (200, 900), 48, '<^')", (500, 940), 32, '<.')

04_loops_sin_plot.py

04_loops_sin_plot

04_loops_sin_plot.py
from drawzero import *
from math import sin

line('blue', 0, 500, 1000, 500)
line('blue', 500, 0, 500, 1000)

STEP = 4
SCALE = 100
for x1 in range(0, 1000, STEP):
    y1 = 500 + SCALE * sin(x1 / SCALE)
    x2 = x1 + STEP
    y2 = 500 + SCALE * sin(x2 / SCALE)
    line('red', (x1, y1), (x2, y2))

Точки и черепашья графика

Класс Pt из публичного API работает и как вектор, и как "черепаха". Эти примеры показывают арифметику точек и построение фигур через повороты и смещения.

05_points.py

05_points

05_points.py
from drawzero import *

# just coordinates
A = Pt(100, 100)
B = Pt(300, 150)
line(C.red, A, B)

# A point which acts as 2-d vector and as a Turtle
# Pt(x=0.0, y=0.0, *, heading=0.0)
#
# Provides (for a, b — points, k number):
#   * arithmetic
#     a+b — vector addition
#     a-b — vector subtraction
#     k*a and a*k — multiplication with scalar
#     abs  — absolute value of a
#     +a, -a

# arithmetic
A = Pt(100, 200)
dx = Pt(50, 5)
for i in range(10):
    filled_circle(C.blue, A + dx * i, radius=10)

#   * turtle-style movement
#     forward — Move the point forward by the specified distance.
#     backward — Move the point backward by distance.
#     right — Turn point right by angle degrees.
#     left — Turn point left by angle degrees.
#     goto — Move point to an absolute position.
#     rotate_around — Rotate around given point by angle degrees.
#     move_towards — Move towards the given point by the specified distance.
#     reset, home — Move point to the origin - coordinates (0,0), set heading=0
#     setx — Set the point's first coordinate to x
#     sety — Set the point's second coordinate to y
#     setheading — Set the point's heading

A = Pt(100, 300)
B = Pt(1000, 400)
for i in range(10):
    filled_circle(C.green2, A, radius=10)
    A.move_towards(50, B)

A = Pt(100, 400)
for i in range(10):
    filled_circle(C.magenta, A, radius=10)
    A.left(10).forward(30)

#   * information
#     position — Return the point's current location (x,y), as a tuple.
#     x, xcor — Return the point's x coordinate.
#     y, ycor — Return the point's y coordinate
#     heading — Return the point's heading
#     distance — Return the distance between points
#     towards — Return the angle towards point
#   * deep copy
#     copy — a clone of point

06_turtle_style.py

06_turtle_style

06_turtle_style.py
from drawzero import *

colors = [C.darkslategrey, C.dimgrey, C.slateblue, C.darkblue, C.yellowgreen, C.maroon,
          C.violetred, C.darkslategray, C.honeydew, C.mediumpurple, C.lightsalmon]

# Drawing regular polygons for n from 3 to 10
# Рисуем правильные треугольник (n=3), четырёхугольник и т.д. до десятиугольник
for n in range(3, 11):
    cur = Pt(600, 800)
    # Drawing n lines for regular polygon
    # Каждую сторону рисуем отдельно
    for i in range(n):
        prev = cur.copy()
        # How to get next point? We clone the old one, rotate left and move forward a bit
        # Чтобы получить новую вершину, мы клонируем старую, поворачиваемся и двигаемся вперёд
        cur.left(360 / n)
        cur.forward(200)
        line(colors[n], prev, cur)

Анимация

Здесь начинается анимация: цикл while True, tick(), clear() и дополнительные эффекты, которые встречаются в примерах 07–10.

07_animation_circles.py

07_animation_circles

07_animation_circles.py
from drawzero import *

# Animations are straightforward
for i in range(0, 60 * 5, 3):
    # Take color at "random"
    # Выбираем цвет псевдослучайно
    color = (i % 255, (19 * i) % 255, (91 * i) % 255)
    x = 100 + 2 * i
    y = 100 + i // 5
    r = 20 + 4 * (i % 5)
    circle(color, (x, y), r)
    # Sleep for 1/30 second
    # Самое главное для анимации — ждём 1/30 секунды
    tick()

08_animation_traffic_light.py

08_animation_traffic_light

08_animation_traffic_light.py
from drawzero import *

# С — is an object with colors as attributes.
# С — специальный объект, у которого есть атрибуты — предопределённые цвета
# https://www.pygame.org/docs/ref/color_list.html
red_on = C.red
red_off = C.red4
yellow_on = C.yellow
yellow_off = C.yellow4
green_on = C.green
green_off = C.green4

fill(C.white)


def traf_light(red, yellow, green):
    """Function to draw signals with given colors
    """
    filled_rect(C.black, (400, 300, 200, 480))
    filled_circle(green, (500, 680), 60)
    filled_circle(yellow, (500, 540), 60)
    filled_circle(red, (500, 400), 60)


for i in range(3):
    traf_light(red_on, yellow_off, green_off)
    sleep(1)
    traf_light(red_off, yellow_on, green_off)
    sleep(1)
    traf_light(red_off, yellow_off, green_on)
    sleep(1)
    traf_light(red_off, yellow_on, green_off)
    sleep(1)

09_animation_rectangles.py

09_animation_rectangles

09_animation_rectangles.py
"""
In this example we use Pt class for turtle-like coordinate manipulations.
Используем класс Pt, позволяющий менять координаты в "черепашьем" стиле
"""
from drawzero import *

screen_center = Pt(500, 500)

small_rect_size = Pt(50, 30)
small_rect_center = screen_center + Pt(300, 0)

big_rect_size = Pt(100, 80)
big_rect_center = screen_center.copy()
for i in range(720):
    # First we make all calculations for the next frame

    # Rotate the big rectangle left by 3 degrees
    big_rect_center.left(3)
    # Line from the center outside the canvas using big rect heading
    laser = big_rect_center.copy().forward(700)
    # Rotate the small rect around the screen center for 1 degree
    small_rect_center.rotate_around(1, screen_center)
    # Rotate the small rect by 5 degrees
    small_rect_center.right(5)

    # Sleep 1/30 second
    tick()
    # No we clear the canvas and draw the next frame
    clear()

    # We need to subtract size/2 to get left upper rect position
    filled_rect_rotated(C.green, big_rect_center - big_rect_size / 2, big_rect_size, angle=big_rect_center.heading)
    # Sometimes we draw laser
    if i % 37 <= 4:
        line(C.orange, screen_center, laser, line_width=10, alpha=140)
    # Transparent small rectangle orbit
    circle(C.yellow, screen_center, 300, line_width=1, alpha=50)
    filled_rect_rotated(C.red, small_rect_center - small_rect_size / 2, small_rect_size, angle=small_rect_center.heading)
    circle(C.violet, screen_center, 10)

10_animation_planets.py

10_animation_planets

10_animation_planets.py
from drawzero import *
from math import sin, cos, pi

earth_orbit = 400
earth_radius = 30
earth_rot_step = 2 * pi / 360
moon_orbit = 100
moon_radius = 10
moon_rot_step = 2 * pi / 60

for i in range(360 * 2):
    # First we make all calculations for the next frame
    e_x = 500 + earth_orbit * cos(earth_rot_step * i)
    e_y = 500 + earth_orbit * sin(earth_rot_step * i)
    m_x = e_x + moon_orbit * cos(moon_rot_step * i)
    m_y = e_y + moon_orbit * sin(moon_rot_step * i)

    # Sleep 1/30 second
    tick()
    # No we clear the canvas and draw the next frame
    clear()

    filled_circle(C.red, (500, 500), 100)
    filled_circle(C.blue, (e_x, e_y), earth_radius)
    filled_circle(C.yellow, (m_x, m_y), moon_radius)

Продвинутое рисование

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

11_transparency_and_line_width.py

11_transparency_and_line_width

11_transparency_and_line_width.py
from drawzero import *

filled_circle('red', (100, 100), 20)
# Set line_width to change line_width
circle('red', (100, 100), 50, line_width=10)
# Set alpha from 0 to 255 to use transparency
filled_circle('blue', (100, 110), 22, alpha=100)
# Or user RGBA for color (RGBA stands for red green blue alpha)
circle((0, 255, 0, 50), (100, 110), 50, line_width=10)

filled_rect(C.aquamarine, (200, 100), 100, 40)
filled_rect(C.darkmagenta, (210, 110), 100, 40, alpha=80)
rect(C.darkgoldenrod, (180, 90), 200, 80, line_width=10)
rect(C.hotpink, (190, 90), 200, 90, alpha=180, line_width=10)

line('red', 600, 400, 600, 990)

polygon('yellow', [(20, 300), (100, 340), (40, 260)], line_width=20)
polygon((0, 0, 255, 200), [(20, 300), (100, 340), (40, 260)], line_width=15)
polygon('red', [(20, 300), (100, 340), (40, 260)])

filled_polygon('burlywood', 200, 600, 130, 504, 20, 542, 20, 658, 130, 696)
filled_polygon(C.hotpink, 200, 700, 130, 604, 20, 642, 20, 758, 130, 796, alpha=100)

line(C.green, (700, 100), (800, 200))
line(C.green, (710, 100), (810, 200), line_width=5)
line(C.red, (820, 100), (720, 200), line_width=10, alpha=50)
line(C.blue, (830, 100), (730, 200), line_width=10, alpha=128)

# Alpha channel is straightforward
rect('yellow', (500, 100), 100, 700, line_width=30, alpha=255)  # via alpha
rect('#00FFFF', (520, 120), 100, 700, line_width=30, alpha=100)  # via alpha
filled_rect((0, 255, 0, 50), (100, 500), 700, 100)  # via rgba

ellipse('grey', (100, 850), 200, 100, alpha=100)
filled_ellipse('red', (100 + 50, 850 + 25), 100, 50, alpha=100)
arc('blue', (200, 850), 200, 100, start_angle=45, stop_angle=270, alpha=100, line_width=10)

fill(C.magenta, alpha=30)

12_images.py

12_images

12_images.py
from pathlib import Path
from drawzero import *

this_file_dir = Path(__file__).parent.absolute()

image(this_file_dir / 'cat.png', (0, 0))
image(this_file_dir / 'cat.png', (500, 500), width=500)

image(this_file_dir / 'cat.png', (100, 600), width=200, alpha=128)
image(this_file_dir / 'cat.png', (200, 700), width=200, alpha=128)
image(this_file_dir / 'cat.png', (300, 800), width=200, alpha=128)

13_gradients.py

13_gradients

13_gradients.py
from drawzero import *


scale1 = Gradient([C.black, C.white], 0, 1000)
for x in range(0, 1000, 10):
    filled_rect(scale1(x), (x, 0), 10, 100)
text(C.white, 'scale1 = Gradient([C.black, C.white], 0, 1000)', (50, 100), 48, '<^')

scale2 = Gradient([C.green, C.yellow, C.magenta, C.red], 0, 1000)
for x in range(0, 1000, 10):
    filled_rect(scale2(x), (x, 200), 10, 100)
text(C.white, 'scale2 = Gradient([C.green, C.yellow, C.magenta, C.red], 0, 1000)', (50, 300), 32, '<^')

scale3 = Gradient([C.white, C.black, C.red, C.black, C.white], 200, 800)
for x in range(0, 1000, 10):
    filled_rect(scale3(x), (x, 400), 10, 100)
text(C.white, 'scale3 = Gradient([C.white, C.black, C.red, C.black, C.white], 200, 800)', (50, 500), 32, '<^')

Интерактивные примеры

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

14_animation_close_vertex.py

14_animation_close_vertex

14_animation_close_vertex.py
from drawzero import *
from random import randint, uniform
from itertools import combinations

NUM_POINTS = 100
MIN_DIST = 150
MAX_SPEED = 4
"""
Here we use Pt class for coordinates. It looks like a tuple with .x and .y methods (and many other).
Здесь мы используем класс Pt для работы с координатами. Почти кортеж, только изменяемый с атрибутами .x и .y
"""
points = [
    (Pt(randint(0, 1000), randint(0, 1000)), Pt(uniform(-MAX_SPEED, MAX_SPEED), uniform(-MAX_SPEED, MAX_SPEED)))
    for __ in range(NUM_POINTS)
]
scale = Gradient(['#5cc3e6', C.black], 0, MIN_DIST)

for i in range(30 * 30):
    # First we make all calculations for the next frame
    for pt, v in points:
        pt.x = (pt.x + v.x) % 1000
        pt.y = (pt.y + v.y) % 1000

    # Sleep 1/30 second
    tick()
    # No we clear the canvas and draw the next frame
    clear()

    for (pt1, v1), (pt2, v2) in combinations(points, r=2):
        dist = pt1.distance(pt2)
        if dist < MIN_DIST:
            color = scale(dist)
            line(color, pt1, pt2, line_width=1)
    for pt, v in points:
        filled_circle('blue', pt, 5)
    fps()

15_animation_firework.py

15_animation_firework

15_animation_firework.py
from drawzero import *
import random
import math
from typing import List

G = -2.0


class Particle:
    vx: float
    vy: float
    cx: float
    cy: float
    color: tuple
    alive: bool
    max_age: int
    age: int = 0
    GLOW = 20
    START_SPEED = 20
    MAX_SIZE = 4
    SCALE = Gradient([C.yellow, C.red], 0, GLOW)

    def __init__(p, x, y):
        random_angle = random.uniform(0, 2 * math.pi)
        random_speed = random.uniform(p.START_SPEED * 0.5, p.START_SPEED * 1.5)
        p.vx = math.cos(random_angle) * random_speed
        p.vy = math.sin(random_angle) * random_speed
        p.color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        p.cx, p.cy = x, y
        p.max_age = random.randint(5, p.GLOW)
        p.alive = True

    def draw(p):
        if p.age < p.GLOW:
            color = p.SCALE(p.age)
            filled_circle(color, (p.cx, p.cy), p.MAX_SIZE * (p.GLOW - p.age) / p.GLOW)

    def update(p):
        p.cx += p.vx
        p.cy += p.vy
        p.vy -= G
        p.age += 1
        if p.cy > 1000 or p.age > p.max_age:
            p.alive = False


class Firework:
    STICK_WIDTH = 5
    HANDLE = 200
    NEW_PARTICLES = 25
    HANDLE_COLOR = (64, 64, 64)
    POWDER_COLOR = (255, 255, 255)
    BURNT_COLOR = (102, 102, 102)

    def __init__(self, x, y, height, sleep=2):
        self.ticks = 0
        self.x = x
        self.y = y
        self.height = height
        self.particles: List[Particle] = []
        self.cur_top = 0
        self.sleep = sleep

    def update(self):
        self.ticks += 1
        if self.ticks < 30 * self.sleep or self.cur_top > self.height:
            return
        self.cur_top += 1
        self.update_particles()
        self.remove_particles()
        self.create_new_particles()

    def draw(self):
        self.draw_stick()
        for p in self.particles:
            p.draw()

    def remove_particles(self):
        last_good = -1
        for cur in range(len(self.particles)):
            p = self.particles[cur]
            if p.alive:
                last_good += 1
                self.particles[last_good] = p
        del self.particles[last_good + 1:]

    def update_particles(self):
        for p in self.particles:
            p.update()

    def create_new_particles(self):
        if self.cur_top < self.height - self.HANDLE:
            for __ in range(self.NEW_PARTICLES):
                self.particles.append(Particle(x=self.x, y=self.y + self.cur_top))

    def draw_stick(self):
        w = self.STICK_WIDTH
        real_top = min(self.cur_top, self.height - self.HANDLE)
        filled_rect(self.HANDLE_COLOR, (self.x - w / 2, self.y), w, self.height)
        filled_rect(self.BURNT_COLOR, (self.x - w / 2 - 2, self.y), w + 4, real_top)
        filled_rect(self.POWDER_COLOR, (self.x - w / 2 - 3, self.y + real_top), w + 6, self.height - self.HANDLE - real_top)

    def is_burnt(self):
        return self.cur_top > 0 and len(self.particles) == 0


firework1 = Firework(x=333, y=200, height=600, sleep=1)
firework2 = Firework(x=666, y=400, height=500, sleep=2)

while not firework1.is_burnt() or not firework2.is_burnt():
    firework1.update()
    firework2.update()
    tick()
    clear()
    firework1.draw()
    firework2.draw()
    fps()

sleep(1)
quit()

16_keyboard_and_mouse.py

Удержание клавиш, оси и одно действие при нажатии

16_keyboard_and_mouse.py
from drawzero import *

x = y = 500
color = C.red
presses = 0
while True:
    speed = 12 if keyboard.pressed('shift') else 4
    dx = keyboard.axis(('left', 'a'), ('right', 'd'))
    dy = keyboard.axis(('up', 'w'), ('down', 's'))
    x += speed * dx
    y += speed * dy
    if keyboard.just_pressed('space'):
        presses += 1
        color = C.blue if color == C.red else C.red
    for event in events():
        if event.type == E.MOUSE_DOWN and event.button == M.LEFT:
            x, y = event.pos
    clear()
    text(C.white, 'CONTROLS / STATE', (500, 70), 48)
    text(C.white, 'Arrows or WASD move; Shift speeds up', (500, 150), 32)
    text(C.white, 'Space changes color once; click places the square', (500, 205), 30)
    text(C.cyan, f'Axis: ({dx}, {dy})   Shift: {keyboard.pressed("shift")}', (500, 300), 36)
    text(C.yellow, f'Space presses: {presses}', (500, 360), 36)
    filled_rect(color, (x, y), 35, 35)
    filled_circle(C.yellow, mouse.pos, 6)
    tick(fps=60)

17_mouse_tube.py

17_mouse_tube

17_mouse_tube.py
from drawzero import *

circles = []
tick()
scale = Gradient([C.gray10, C.blue, C.orange], 100, 500)
while True:
    x, y = mouse.pos
    circles.append([x, y, 100])
    clear()
    for i in range(len(circles) - 1, -1, -1):
        x, y, r = circles[i]
        if r > 1000:
            circles.pop(i)
        else:
            circle(scale(r), (x, y), r, line_width=3)
            circles[i][2] += 10
    fps()
    tick()

Игры

И, наконец, несколько мини-игр, собранных из примитивов, анимации и обработки ввода.

18_game_stars.py

18_game_stars

18_game_stars.py
from drawzero import *
from random import randint
from dataclasses import dataclass

from time import perf_counter
NUM_STARS = 300


@dataclass
class Star:
    x: float
    y: float
    z: float
    r: int
    color: tuple


def gen_star():
    '''Создать звезду'''
    x = randint(-1000000 // 2, 1000000 // 2)
    y = randint(1, 1000)
    z = randint(-1000000 // 2, 1000000 // 2)
    r = randint(10, 3000)
    color = (randint(0, 255), randint(0, 255), randint(0, 255))
    return Star(x, y, z, r, color)


def create_stars():
    '''Создать массив звёзд'''
    stars = []
    for i in range(NUM_STARS):
        stars.append(gen_star())
    return stars


def move_stars(stars, speed):
    '''Сдвинуть все звёзды
    Если звезда перестала попадать на экран, то заменяем её на новую'''
    Vx, Vy, Vz = speed
    for i, star in enumerate(stars):
        star.x += Vx
        star.y += Vy
        star.z += Vz
        if (
                not (1 < star.y < 1000)
                or not (-500 < star.x / star.y < 500)
                or not (-500 < star.x / star.y < 500)
        ):
            # Вообще это — так себе решение. Но частично работает
            stars[i] = gen_star()


def draw_stars(stars):
    '''Отрисовать все звёзды'''
    # Сортируем звёзды, чтобы те, которые ближе к экрану, отрисовывались позже
    stars.sort(key=lambda star: -star.y)
    for star in stars:
        y = star.y
        screen_x = 500 + star.x / y
        screen_y = 500 + star.z / y
        screen_r = star.r / y
        filled_circle(star.color, (screen_x, screen_y), screen_r*2)
    text('white', 'Press WASD or QE to move', (500, 5), 48, '.^')


def process_keys(speed):
    '''Обрабатываем нажатия клавиш
    Используем WASD для вверх/вниз/влево/вправо и QE для вперёд/назад'''
    if keyboard.pressed(K.UP) or keyboard.pressed(K.w):
        speed[2] += 100
    if keyboard.pressed(K.DOWN) or keyboard.pressed(K.s):
        speed[2] -= 100
    if keyboard.pressed(K.LEFT) or keyboard.pressed(K.a):
        speed[0] += 100
    if keyboard.pressed(K.RIGHT) or keyboard.pressed(K.d):
        speed[0] -= 100
    if keyboard.pressed(K.q):
        speed[1] -= 1
    if keyboard.pressed(K.e):
        speed[1] += 1


# Здесь ставим размер экрана
stars = create_stars()
# Текущая скорость, стартуем с 0
speed = [0, 0, 0]
while True:
    # Заливаем всё чёрным
    fill((0, 0, 0))
    # Обрабатываем нажатия клавиш
    process_keys(speed)
    # Двигаем звёзды
    move_stars(stars, speed)
    # Рисуем звёзды
    draw_stars(stars)
    # Ждём 1/30 секунды
    tick()

19_game_colors.py

19_game_colors

19_game_colors.py
from drawzero import *
import random
from time import time

COLORS = [('yellow', 'yellow'), ('green', 'green'), ('cyan', '#42aaff'), ('blue', '#0000ff'), ('purple', 'purple'),
          ('red', 'red'), ('orange', 'orange'), ('brown', 'brown'), ('gray', 'gray'), ('white', 'white'), ]


def gen_new():
    cur_word, color = random.choice(COLORS)
    corr = True
    if random.random() < 0.5:
        corr = False
        new_color = color
        while new_color == color:
            __, color = random.choice(COLORS)
    return cur_word, color, corr


score = 0
cur_timelimit = 4
cur_word, color, corr = gen_new()
cur_status = None
round_start = last_status_ts = time()
while True:
    cur_ts = time()
    time_left = round_start + cur_timelimit - cur_ts
    if time_left < 0:
        score -= 1
        cur_timelimit *= 1.02
        cur_word, color, corr = gen_new()
        round_start = last_status_ts = cur_ts
        cur_status = None
        time_left = cur_timelimit
    clear()
    text('white', f'Scores: {score}', (500, 50), 48)
    text('white', f'Double-click the left mouse button,', (500, 100), 32)
    text('white', f'if the word and color match, otherwise right-click', (500, 142), 32)
    text('white', f'{time_left:0.2f}s...', (500, 600), 48)
    text(color, cur_word, (500, 500), 120)
    if cur_status == 1:
        text('green', 'Match?', (500, 700), 48)
    elif cur_status == 3:
        text('red', 'Mismatch?', (500, 800), 48)
    # Ignore clicks for half a second after color change
    for ev in mouse.events:
        if ev.type != E.MOUSE_DOWN:
            continue
        if cur_ts - last_status_ts > 0.3:
            if ev.button == cur_status:
                # User confirmed
                if (corr and cur_status == 1) or (not corr and cur_status == 3):
                    score += 1
                    cur_timelimit *= 0.95
                else:
                    score -= 1
                    cur_timelimit *= 1.02
                cur_word, color, corr = gen_new()
                round_start = last_status_ts = cur_ts
                cur_status = None
            elif ev.button in (1, 3):
                cur_status = ev.button
                last_status_ts = cur_ts
    tick()

20_game_racing.py

20_game_racing

20_game_racing.py
from drawzero import *

cars_y = [540, 500, 460]
cars_x = [100, 100, 100]
ups = [K.w, K.i, K.UP]
downs = [K.s, K.k, K.DOWN]
lefts = [K.a, K.j, K.LEFT]
rights = [K.d, K.l, K.RIGHT]
colors = ['green', 'blue', 'yellow']
scores = [1000, 1000, 1000]

road = [500] * 30 + list(range(500, 700, 3)) + list(range(700, 900, 6)) + list(range(900, 100, -4)) + list(
    range(100, 500, 8))
road.extend(road)
road.extend(road)
WIDTH2 = 80
road_pos = 0
for i in 3, 2, 1:
    clear()
    text('green', 'READY? ' + str(i), (300, 400), 128)
    sleep(1)

while True:
    for i in range(len(cars_y)):
        if keyboard.pressed(ups[i]):
            cars_y[i] -= 5
        if keyboard.pressed(downs[i]):
            cars_y[i] += 5
        if keyboard.pressed(lefts[i]):
            cars_x[i] -= 5
        if keyboard.pressed(rights[i]):
            cars_x[i] += 5
    clear()
    for i in range(len(cars_y)):
        filled_rect(colors[i], (cars_x[i], cars_y[i] - 20), 80, 40, alpha=70)
    for i in range(100):
        line('red', i * 10, road[i + road_pos] - WIDTH2, i * 10 + 10, road[i + 1 + road_pos] - WIDTH2)
        line('red', i * 10, road[i + road_pos] + WIDTH2, i * 10 + 10, road[i + 1 + road_pos] + WIDTH2)
    for i in range(len(cars_y)):
        if cars_y[i] - 20 < road[cars_x[i] // 10 + road_pos] - WIDTH2:
            text(colors[i], 'Boom!', (300 + 200 * i, 10), 72)
            cars_y[i] = road[cars_x[i] // 10 + road_pos] - WIDTH2 + 20
            scores[i] -= 1
        elif cars_y[i] + 20 > road[cars_x[i] // 10 + road_pos] + WIDTH2:
            text(colors[i], 'Boom!', (300 + 200 * i, 10), 72)
            cars_y[i] = road[cars_x[i] // 10 + road_pos] + WIDTH2 - 20
            scores[i] -= 1
        text(colors[i], str(scores[i]), (300 + 200 * i, 100), 72)

    tick()
    road_pos += 1
    if road_pos > 1000:
        i = scores.index(max(scores))
        text('white', f'{colors[i]} is winner!', (150, 900), 128)
        tick()
        break

21_precise_drawing.py

Точное рисование по событиям мыши

from drawzero import *

segments = []
previous = None
while True:
    for event in events():
        if event.type == E.FOCUS_LOST:
            previous = None
        elif event.type == E.MOUSE_DOWN and event.button == M.LEFT:
            previous = event.pos
        elif event.type == E.MOUSE_MOVE:
            if previous is not None and M.LEFT in event.buttons:
                segments.append((previous, event.pos))
                previous = event.pos
        elif event.type == E.MOUSE_UP and event.button == M.LEFT:
            if previous is not None and not event.source.cancelled:
                segments.append((previous, event.pos))
            previous = None
    if keyboard.just_pressed('space'):
        segments.clear()
    clear()
    text(C.white, 'PRECISE DRAWING', (500, 70), 48)
    text(C.white, 'Hold left mouse button to draw; Space clears', (500, 150), 32)
    for start, end in segments:
        line(C.cyan, start, end, line_width=6)
    filled_circle(C.yellow, mouse.pos, 8)
    text(C.gray, f'Path segments: {len(segments)}', (500, 900), 32)
    tick(fps=60)

22_text_and_scroll.py

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

from drawzero import *

value = ''
scroll_x = scroll_y = 0
composition = ''
while True:
    for event in events():
        if event.type == E.TEXT_INPUT:
            value += event.text
            composition = ''
        elif event.type == E.TEXT_EDITING:
            composition = event.text
        elif event.type == E.KEY_DOWN and event.key == K.BACKSPACE:
            value = value[:-1]
        elif event.type == E.FOCUS_LOST:
            composition = ''
    dx, dy = mouse.wheel
    scroll_x += dx
    scroll_y += dy
    clear()
    text(C.white, 'TEXT / TWO-DIMENSIONAL SCROLL', (500, 70), 40)
    text(C.white, 'Type text; Backspace deletes; scroll moves the marker', (500, 150), 28)
    text(C.cyan, value, (80, 300), 48, '<.')
    text(C.gray, composition, (80, 370), 36, '<.')
    text(C.yellow, f'Scroll: ({scroll_x:.1f}, {scroll_y:.1f})', (500, 480), 36)
    line(C.gray, (200, 720), (800, 720))
    line(C.gray, (500, 570), (500, 870))
    filled_circle(C.orange, (500 + 30 * scroll_x, 720 - 30 * scroll_y), 22)
    tick(fps=60)

99_errors.py

import os
import traceback
import sys

os.environ['EJUDGE_MODE'] = 'true'

from drawzero import *

# filled_circle('boo')
# line('nocolor', (100, 100), ('asdf',))
# circle('red', (100, 100), 'asd', line_width=0)
# filled_rect('yellow', (250, 350, 50, 100))  # tuple[x,y,w,h] is also OK


class ExceptionPrinter:
    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, exc_traceback):
        if exc_traceback:
            traceback_len = 0
            cur = exc_traceback
            while cur:
                traceback_len += 1
                cur = cur.tb_next
            print(f'Traceback is {traceback_len} deep')
            print(''.join(traceback.format_tb(exc_traceback)), end='')
            print(exc_value)
        return True


# import drawzero
# print(help(drawzero))
# exit()
#
#
s = ExceptionPrinter()

with s: line('nocolor', (100, 100), (200, 200))
with s: line(C.red, (100, 200), (200, 'baz'))
with s: rect('violet', (100, 200, 300, 'baz'))