Многие смотрели этот легендарный фильм из нескольких частей. Попробуем написать код для создания похожей матрицы символов. Используем для этого 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'). После создаётся поверхность того же цвета, что поверхность окна для их смешивания, чтобы постепенно затенялись символы.
Для символов матрицы взяты иероглифы японского алфавита "катакана", которые начинаются с адреса 0x30A0 по 0x30FF в количестве 16*6=96 штук. Создана папка font рядом с файлом проекта, в который скачан файл шрифта для японских иероглифов "ms mincho.ttf" Из этого файла загружен шрифт определённого размера и преобразован в список символов в цикле. Затем создан список цифр и прочих знаков матрицы.
Затем создано два списка символов зелёного и белого цвета, в которые были отрисованы и зеркально отражены по горизонтали иероглифы, а затем - и цифры со знаками без отражения. Символы теперь представлены маленькими поверхностями-картинками. Такие картинки рисуются быстрее, чем символы шрифта.
Затем инициализируется двумерный массив, содержащий все символы окна, и 11 параметров для контроля потоков символов.
Затем создана процедура обновления потока, в которой параметрам заданного потока присваивает начальные параметры. После чего параметры всех потоков окна обновляются в цикле.
А теперь начинается главный цикл программы. Первоначально поверхность затенения отрисовывается на поверхности окна, чтобы постепенно затенялись символы от предыдущих итераций цикла. Во вложенном цикле перебираются все потоки поочереди. Сперва отсчитывается задержка d[i] для каждого потока в условном операторе перед началом его отрисовки. Затем в первом условном операторе, если поток не отрисован весь, то рисуются сперва зелёные символы потока, а затем светлый. Число рисуемых символов увеличивается на каждой итерации главного цикла за счёт счётчика шагов в потоке cs[i].
Далее отрисовываются случайные символы из списка зелёных символов, но прорисовка происходит не мгновенно, а каждые несколько итераций. С каждой итерацией счётчик паузы c[i] растёт, который периодически обнуляется и при эом происходит отрисовка символов. Количество отрисованных случайных символов в потоке колеблется случайным образом (от 2 до 5), а общее количество символов в потоке увеличивается, поэтому в цикле отрисовки случайных символов в потоке верхний предел задан через счётчик шагов потока rd.randint(0, cs[i]+1).
Далее отрисовывается случайный символ в поле матрицы, если он был задан при обновлении потока, и увеличиваются счётчик шагов потока и его координата с шагом размера шрифта FONT_SIZE.
Если счётчик шагов потока достиг или превысил его максимальную длительность, а также ушёл за границу окна, то поток считается заполненным. При этом обновляется его длина, если счётчик шагов цикла превысил его длительность или ушёл за границы окна, и сбрасывается счётчик шагов потока на 1. Теперь он будет считать уменьшение потока.
Если поток заполнен, то он постепенно затирается от первого символа к последнему, но не сразу, а спустя некоторое время. Удаление символов потока после его заполнения контролируется через счётчик паузы p[i]. Когда же пауза заканчивается, что проверяется в условном операторе, счётчик шагов потока начинает вновь расти, что влияет на постепенное затирание символов с начала потока, а не с конца. Это обеспечивается в задании пределов цикла отрисовки оставшихся символов. Также продолжает отрисовываться случайный символ в поле матрицы, пока поток не будет полностью стёрт. В итоге поток обновляется и логика работы повторяется.
В конце программы создан небольшой обработчик событий. Он завершает программу при закрытии окна или нажатии клавиши Esc. Далее происходит отрисовка кадра окна на экране и задание частоты кадров обновления окна.
В итоге вышел результат очень похожий на то, что видно в фильме "Матрица".
Далее идёт код программы, но без отступов. Их убирает Дзен при форматировании текста в момент публикации.
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) #установка частоты кадров