Home Settings
Aa

Тьюринг-полнота CSS

Если загуглить что-то вроде "Является ли CSS полным по Тьюрингу", первыми ответами в поисковом запросе будут реализации Правила 110 - одномерного клеточного автомата с доказанной полнотой по Тьюрингу. Пример другого известного клеточного автомата - игра "Жизнь". Он тоже полон по Тьюрингу, но излишне сложен в силу двухмерности.

Rule 110 illustration Первые 16 шагов правила 110, Стартовая позиция - 1 клетка в центре. Источник

Что уже есть

Реализаций можно найти множество: на Stackoverflow, на Github, на сайтах других людей. Однако все, что я нашёл, имеют существенное "но" - HTML + CSS страничка во всех этих примерах требует от пользователя прокликивать подсвеченные ячейки. Данное действие тоже автоматизируется, например в этом примере выше достаточно многократно нажимать Tab и пробел, однако у меня возник вопрос: можно ли минимизировать ввод от пользователя, сохранив функциональность?

Чем можно воспользоваться

С 2016 года большинство современных браузеров начали поддерживать "пользовательские свойства", или, проще говоря, переменные в CSS. Это может звучать как ультимативное решение, однако стоит учесть их ограничения: в CSS нет массивов, нет логического типа данных, есть всего 4 базовые арифметические операции (стандартные +, -, * и /), и, что самое главное - куда сложнее организовать подобие циклов.

Любой CSS с рекурсивным вычислением переменных в одной области видимости автоматически перестаёт вычислять их значения, так что придётся хитрить. Первая идея - использовать рекурсивно вложенные блоки. Так как CSS - это каскадные таблицы стилей, значение переменной, вычисленной в конкретном блоке, доступно только в нём и его потомках. Но его можно переопределить в любом подблоке, как в более локальной области видимости, по аналогии с обычными языками программирования.

Саму логику вычислений можно организовать, используя числа 1 и 0 и логические операторы из арифметических операций. Я нашёл целую статью про то как их можно собрать и применить на практике для 3D на чистом CSS.

Формула правила 110 есть на русской википедии (на английской нет): (p, q, r) ↦ (q AND (NOT p)) OR (q XOR r).

Простейший способ записать NOT арифметически - (1 - x), AND - (x * y). Этих двух операторов уже достаточно для функционально полной системы, и из них легко выводится OR по закону де Моргана - (1 - (1 - x) * (1 - y)), и XOR - (x + y - 2 * x * y).

Итоговая формула в арифметической записи:

1
2
3
4
5
Copy
1 - (
    1 - (q * (1 - p))
) * (
    1 - (q + r - 2 * q * r)
)

Первая реализация

Висит тут. Исходник на Jinja.

Ввод данных работает так же, как и в других примерах - на чекбоксах в первой строке.

Во избежание циклических формул каждая последующая строка находится на 2 уровня вложенности глубже, чем предыдущая: на первом значение из переменных v дублируется в b, а на втором значение более локальных v вычисляется по формуле из b. Итоговое количество переменных - по две на столбец, с нумерацией с нуля: --v0 и --b0, --v1 и --b1 и так далее.

Вывод данных осуществляется при помощи классов ci, которые меняют цвет по формуле hsl(0, 0%, calc(100% - 100% * var(--vi))), становясь чёрными, если соответствующая переменная --vi равняется единице.

После небольшой доработки декоративной части пример заработал... первые 4 шага.

Rule110 v0 Строка ввода, вычисленные 4 шага и пустые ряды внизу.

Оптимизация

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

Опытным путём выяснилось, что браузеру не важно количество операторов в формуле, но важно количество использованных в формуле упоминаний переменных - чем меньше ссылок на предыдущий ряд, тем больше строк вычисляется. В формуле в Википедии в записи с логическими операторами переменных всего 4, однако оператор XOR в аналитическом виде имеет в себе по 2 записи входных переменных, итого 6. У меня получилось сократить её до 5: (p, q, r) ↦ (q OR r) AND NOT (p AND q AND r) с соответствующей куда более короткой аналитической записью: (1 - (1 - q) * (1 - r)) * (1 - p * q * r).

Страничка c новой формулой, её исходник. Оптимизация формулы добавляет по ещё одному шагу как в Chromium, так и в Firefox, что позволяет в полной мере раскрыть потенциал правила и вывести амогуса.

Rule110 v1 sus.

Любопытному читателю предлагается (в интересах автора, конечно же) попробовать оптимизировать формулу ещё сильнее или доказать невозможность выразить её записью менее чем с 5 переменными.

Второй идеей для оптимизации было попробовать убрать промежуточные переменные --bi, для чего я убрал вложенность и сделал отдельную переменную для каждой ячейки, но как оказалось это ни на что не влияет - как не ухудшает ситуацию из-за 100 отдельных переменных в CSS вместо 20, так и не улучшает из-за отсутствия дублирования значений и меньшей вложенности.

В любом случае посмотреть на попытку можно тут, исходник.

Rule110 full result Последняя версия в Firefox

Можно ли это применить?

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

Minesweeper

Не пригодившееся

Помимо статьи про логические операторы, я нашёл и статью про более сложные математические функции (для реализации 3D): модуль, сигнум, округление и остаток от деления, но все они построены на экспериментальном правиле @property, которое пока что поддерживается не всеми браузерами.