Найти в Дзене
Мысли вслух

Заставка "Матрица" на Python.

Постер фильма "Матрица"
Постер фильма "Матрица"

Многие смотрели этот легендарный фильм из нескольких частей. Попробуем написать код для создания похожей матрицы символов. Используем для этого Python. В первой части фильма момент, где близко и разборчиво видна матрица символов - при первом разговоре Сайфера и Нео. Возможно, некоторые из читателей знают, что символы в матрице взяты из рецепта суши, а японские иероглифы были зеркально отображены.

Общий вид
Общий вид
Центральная часть
Центральная часть

Задача кажется простой, но это лишь на первый взгляд. Проведём анализ роликов и сделаем список особенностей этой матрицы. Наблюдения эти субъективны и не являются истиной в последней инстанции!
1)
Первые символы потока светлые, остальные - зелёные. Цвет фона чёрный.
2)
Большая часть потоков начинается сверху и заканчивается внизу, но малая часть потоков начинается и заканчивается в любой части матрицы.
3)
Второй символ потока повторяет первый, напечатанные символы потока статичны, они не сдвигаются.
4) Часть символов потока могут меняться на другие, а остальные прежние.
5) Спустя некоторое время, после достижения конечной точки потоком, он начинает последовательно исчезать от начального символа.
6) При исчезновении потока и изменении части его символов,
старые символы исчезают не мгновенно, а с затуханием в течении очень короткого промежутка времени.
7) Очень малая часть символов распределена по матрице.
8)
Элементы в матрице - зеркально отраженные по горизонтали японские иероглифы, а также не отзеркаленные цифры, знаки препинания и знаки математических действий.
9) Символы каждого потока определены заранее.
10) Скорость появления и исчезновения символов одинакова для всех потоков.

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

Для подобной работы в Python используем библиотеку pygame, для создания окна и работы со шрифтами, и random, для работы со случайными числами. Среда разработки - Visual Studio Code.

Код программы содержит 138 строк вместе с пустыми строками для разделения на блоки. В скринах кода программы можно разглядеть комментарий для каждой строки. Начинается код с импорта двух вышеупомянутых модулей pygame и random. Задаются параметры будущего окна и самой программы, а затем инициализируется модуль pygame и создаётся окно. При задании иконки скопируйте любую картинку, желательно небольших размеров и в формате jpg, в папку с файлом проекта и укажите её имя в команде pg.image.load('Matrix.jpg'). После создаётся поверхность того же цвета, что поверхность окна для их смешивания, чтобы постепенно затенялись символы.

Код программы. Строки 1 - 23
Код программы. Строки 1 - 23

Для символов матрицы взяты иероглифы японского алфавита "катакана", которые начинаются с адреса 0x30A0 по 0x30FF в количестве 16*6=96 штук. Создана папка font рядом с файлом проекта, в который скачан файл шрифта для японских иероглифов "ms mincho.ttf" Из этого файла загружен шрифт определённого размера и преобразован в список символов в цикле. Затем создан список цифр и прочих знаков матрицы.

Код программы. Строки 24 - 33
Код программы. Строки 24 - 33

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

Код программы. Строки 34 - 53
Код программы. Строки 34 - 53

Затем инициализируется двумерный массив, содержащий все символы окна, и 11 параметров для контроля потоков символов.

Код программы. Строки 54 - 70
Код программы. Строки 54 - 70

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

Код программы. Строки 71 - 90
Код программы. Строки 71 - 90

А теперь начинается главный цикл программы. Первоначально поверхность затенения отрисовывается на поверхности окна, чтобы постепенно затенялись символы от предыдущих итераций цикла. Во вложенном цикле перебираются все потоки поочереди. Сперва отсчитывается задержка d[i] для каждого потока в условном операторе перед началом его отрисовки. Затем в первом условном операторе, если поток не отрисован весь, то рисуются сперва зелёные символы потока, а затем светлый. Число рисуемых символов увеличивается на каждой итерации главного цикла за счёт счётчика шагов в потоке cs[i].

Далее отрисовываются случайные символы из списка зелёных символов, но прорисовка происходит не мгновенно, а каждые несколько итераций. С каждой итерацией счётчик паузы c[i] растёт, который периодически обнуляется и при эом происходит отрисовка символов. Количество отрисованных случайных символов в потоке колеблется случайным образом (от 2 до 5), а общее количество символов в потоке увеличивается, поэтому в цикле отрисовки случайных символов в потоке верхний предел задан через счётчик шагов потока rd.randint(0, cs[i]+1).

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

Если счётчик шагов потока достиг или превысил его максимальную длительность, а также ушёл за границу окна, то поток считается заполненным. При этом обновляется его длина, если счётчик шагов цикла превысил его длительность или ушёл за границы окна, и сбрасывается счётчик шагов потока на 1. Теперь он будет считать уменьшение потока.

Код программы. Строки 90 - 118
Код программы. Строки 90 - 118

Если поток заполнен, то он постепенно затирается от первого символа к последнему, но не сразу, а спустя некоторое время. Удаление символов потока после его заполнения контролируется через счётчик паузы p[i]. Когда же пауза заканчивается, что проверяется в условном операторе, счётчик шагов потока начинает вновь расти, что влияет на постепенное затирание символов с начала потока, а не с конца. Это обеспечивается в задании пределов цикла отрисовки оставшихся символов. Также продолжает отрисовываться случайный символ в поле матрицы, пока поток не будет полностью стёрт. В итоге поток обновляется и логика работы повторяется.

В конце программы создан небольшой обработчик событий. Он завершает программу при закрытии окна или нажатии клавиши Esc. Далее происходит отрисовка кадра окна на экране и задание частоты кадров обновления окна.

Код программы. Строки 119 - 138
Код программы. Строки 119 - 138

В итоге вышел результат очень похожий на то, что видно в фильме "Матрица".

Результат программы
Результат программы

Далее идёт код программы, но без отступов. Их убирает Дзен при форматировании текста в момент публикации.

import pygame as pg #импорт библиотеки pygame
import random as rd #импорт библиотеки random

RES = WIDTH, HEIGHT = 800, 600 #размер окна
FONT_SIZE = 20 #размер шрифта
nf = WIDTH // FONT_SIZE #число потоков
mlf = HEIGHT // FONT_SIZE #максимальная длина потока
frame = 10 #число кадров
alpha_value = 150 #сила затухания

pg.init() #инициализация pygame
screen = pg.display.set_mode(RES) #создание окна
icon = pg.image.load('Matrix.jpg') #загрузка картинки для иконки
pg.display.set_icon(icon) #установка иконки для окна
pg.display.set_caption('Matrix code') #название окна

surface = pg.Surface(RES) #создание поверхности для затухания символов
surface.set_alpha(alpha_value) #установка прозрачности поверхности для затухания
clock = pg.time.Clock() #создание часов для контроля частоты кадров

screen.fill((0, 0, 0)) #цвет фона окна
surface.fill((0, 0, 0)) #закрашивание в чёрный цвет поверхность затухания

font = pg.font.Font('font/ms mincho.ttf', FONT_SIZE) #загрузка шрифта "ms mincho.ttf" из папки font
font.bold = True #используем жирный шрифт
katakana = ['0'] * 96 #инициализация списка для 96 иероглифов японского алфавита "катакана"
#96 иероглифов katakana в файле "ms mincho.ttf" находятся по адресу '0x30a0'. Адрес в 16-ричном формате
for i in range(96):
katakana[i] = chr(int('0x30a0', 16) + i) #получение символа по его адресу в файле шрифта

string_of_numbers = "1234567890=-+*<>|/:" #добавление строки цифр и прочих знаков
number_of_symbols = 96 + len(string_of_numbers) #общее количество символов

#Отобразим светлые и зелёные символы из katakana в линейные массивы
green_katakana = [0] * number_of_symbols #инициализация списка символов в зелёном цвете
for i in range(96): #цикл из 96 итераций
green_katakana[i] = font.render(katakana[i], True, (50, 200, 50)) #рендеринг зелёного символа
pg.transform.flip(green_katakana[i], True, False) #зеркальное отображение символов по горизонтали

light_katakana = [0] * number_of_symbols #инициализация списка символов в белом цвете
for i in range(96): #цикл из 96 итераций
light_katakana[i] = font.render(katakana[i], True, (255, 255, 255)) #рендеринг белого символа
pg.transform.flip(light_katakana[i], True, False) #зеркальное отображение символов по горизонтали

font = pg.font.Font('font/ms mincho.ttf', FONT_SIZE + 2) #загрузка шрифта для строки цифр
font.bold = True #используем жирный шрифт

#Добавляем строку цифр к иероглифам
for i in range(len(string_of_numbers)): #цикл по количеству символов в строке цифр
green_katakana[96 + i] = font.render(string_of_numbers[i], True, (50, 200, 50)) #рендеринг цифр в зелёном цвете
for i in range(len(string_of_numbers)): #цикл по количеству символов в строке цифр
light_katakana[96 + i] = font.render(string_of_numbers[i], True, (255, 255, 255)) #рендеринг цифр в белом цвете

#Двумерный массив символов потока
fm = [1] * nf #инициализация массива по количеству потоков - первый индекс
for i in range(1, nf): fm[i] = [1] * mlf #инициализация массива по количеству символов в потоке - второй символ
for i in range(1, nf):#заполнение массива позициями символов в общем списке
for j in range(1, mlf): fm[i][j] = rd.randint(1, number_of_symbols - 1)
#Переменные потоков матрицы символов
cs = [1] * nf #текущее количество символов в потоке
df = [1] * nf #максимальное количество символов в потоке
f = [False] * nf #достиг ли поток максимального количества символов
y = [1] * nf #координата потока по высоте окна
x = [1] * nf #координата потока по ширине окна
b = [1] * nf #начальная координата потока по высоте окна
p = [1] * nf #пауза перед стиранием потока после достижения максимального количества символов
d = [1] * nf #задержка перед появлением потока
c = [1] * nf #задержка перед прорисовкой случайного символа в потоке
cm = [1] * nf #случайный символ в поле матрицы
ych = [1] * nf #координата случайного символа в поле матрицы

def new_flow(i):#процедура обновления переменных при создании нового потока матрицы
for j in range(1, mlf): fm[i][j] = rd.randint(1, number_of_symbols - 1) #выбор позиций символов для потока
cs[i] = 0 #обнуление счётчика шагов потока
if rd.random() < 0.9: df[i] = mlf #выбирается макимальное число символов в потоке из первого диапазона
else: df[i] = rd.randint(mlf // 4, mlf) #выбирается макимальное число символов в потоке из второго диапазона
f[i] = False #новый поток пуст и не переполнен
y[i] = 0 #обнуление текущей координаты потока
x[i] = (i-1) * FONT_SIZE #обнуление позиции потока по горизонтали
if rd.random() < 0.8: b[i] = -FONT_SIZE #выбор начальной координаты потока из первого диапазона
else: b[i] = FONT_SIZE * (rd.randint(1, mlf) - 1) #выбор начальной координаты потока из второго диапазона
p[i] = mlf // 5 #обновление счётчика паузы перед стиранием потока после его заполнения
if rd.random() < 0.8: d[i] = rd.randint(mlf // 10, mlf) #задержка перед отрисовкой потока из 1-го диапазона
else: d[i] = rd.randint(mlf // 5, mlf // 2) #задержка перед отрисовкой потока из 2-го диапазона
c[i] = 0 #обнуление счётчика паузы перед отрисовкой нового случайного символа в потоке
cm[i] = rd.choice(green_katakana) #выбор случайного символа для отрисвоки в поле матрицы
if rd.random() > 0.8: ych[i] = rd.randint(1, HEIGHT) #координата случайного символа в поле матрицы из 1-го диапазона
else: ych[i] = HEIGHT + 10 #координата случайного символа в поле матрицы из 2-го диапазона

for i in range(1, nf, 1): new_flow(i) #обновление переменных всех потоков

while True: #главный цикл матрицы символов
screen.blit(surface, (0, 0)) #смешивание поверхности затухания и окна
for i in range(1, nf, 1): #перебираем все потоки поочереди
if ((f[i] == False) and (d[i] <= 1)): #если поток не достиг максимума и задержка его отрисовки прошла
for k in range(cs[i], 0, -1): #рисуем символы потока до выполненого шага
screen.blit(green_katakana[fm[i][k]], (x[i], b[i] + k * FONT_SIZE)) #символы потока
screen.blit(light_katakana[fm[i][cs[i]]], (x[i], b[i] + y[i])) #первый символ потока

#Случайные символы в потоке
if c[i] == 3: #пауза для случайных символов в потоке
c[i] = 0 #обнуление счётчика паузы
for v in range(1, rd.randint(2, 5)): #выбирается количество новых символов в потоке для цикла
#выбор на случайной позиции в потоке для нового случайного символа
fm[i][rd.randint(0, cs[i] + 1)] = rd.randint(1, number_of_symbols - 1)
c[i] = c[i] + 1 #счётчик паузы перед отрисовкой нового символа в потоке

screen.blit(cm[i], (x[i], ych[i])) #случайный символ в поле матрицы

cs[i] = cs[i] + 1 #счётчик шагов потока
y[i] = y[i] + FONT_SIZE #получение координаты для следующего шага

if (cs[i] >= df[i]) or (y[i] >= HEIGHT): #Если шагов потока больше его длины или координата ушла за экран
f[i] = True #поток заполнен
df[i] = cs[i] #обновление длины потока после заполнения
cs[i] = 1 #сброс счётчика шагов потока после его заполнения

if d[i] >= 1: d[i] = d[i] - 1 #счётчик паузы перед отрисовкой потока

if (f[i]): #Если поток заполнен
for k in range(cs[i], df[i], 1): #уменьшаем длину потока от начала к концу
screen.blit(green_katakana[fm[i][k]], (x[i], b[i] + k * FONT_SIZE)) #отрисовывая оставшиеся символы
screen.blit(cm[i], (x[i], ych[i])) #отрисовываем случайные символы в поле матрицы

if p[i] <= 0: #Если счётчик паузы перед стиранием символов потока после его заполнения равен нулю
cs[i] = cs[i] + 1 #счётчик шагов убывания символов потока после его заполнения
p[i] = p[i] - 1 #счётчик паузы перед поочерёдным стиранием символов потока после заполнения

if cs[i] >= df[i]: #Если поток переполнен и шагов больше их максимального числа или равен ему
new_flow(i) #обновляем поток

#Закрытие окна при получении события закрытия
for i in pg.event.get():#перебираются события окна
if i.type == pg.QUIT: exit() #для его закрытия при получении действия о закрытия окна
if ((i.type == pg.KEYDOWN) and (i.key == pg.K_ESCAPE)): exit() #закрытие полсе нажатия клавиши Esc

pg.display.flip() #смена кадра
clock.tick(frame) #установка частоты кадров

Наука
7 млн интересуются