{%- extends "article/base.html" -%}
{%- block meta -%}
{
"date": "2022.04.22",
"number": 1
}
{%- endblock -%}
{%- block title -%}
Тьюринг-полнота CSS
{%- endblock -%}
{%- block description -%}
Создание чуть более близкой к полноте по Тьюрингу веб-странички без JS.
{%- endblock -%}
{%- block article -%}
{%- filter markdown -%}
Если загуглить что-то вроде "Является ли CSS полным по Тьюрингу", первыми ответами в
поисковом запросе будут реализации [Правила 110][R110] - одномерного клеточного автомата
с доказанной полнотой по Тьюрингу. Пример другого известного клеточного автомата -
[игра "Жизнь"][GoL]. Он тоже полон по Тьюрингу, но излишне сложен в силу двухмерности.
![Rule 110 illustration][img_rules]
*Первые 16 шагов правила 110, Стартовая позиция - 1 клетка в центре. [Источник][S]*
[R110]: https://ru.wikipedia.org/wiki/Правило_110
[GoL]: https://ru.wikipedia.org/wiki/Игра_«Жизнь»
[S]: https://mathworld.wolfram.com/ElementaryCellularAutomaton.html
## Что уже есть
Реализаций можно найти множество: на [Stackoverflow][SO], на [Github][GH], на [сайтах
других людей][NL]. Однако все, что я нашёл, имеют существенное "но" - HTML + CSS
страничка во всех этих примерах требует от пользователя прокликивать подсвеченные ячейки.
Данное действие тоже автоматизируется, например в [этом примере][EF] выше достаточно
многократно нажимать Tab и пробел, однако у меня возник вопрос: можно ли минимизировать
ввод от пользователя, сохранив функциональность?
[SO]: https://stackoverflow.com/a/5239256/11842314
[GH]: https://github.com/brandondong/css-turing-machine
[NL]: https://notlaura.com/is-css-turing-complete
[EF]: http://eli.fox-epste.in/rule110-full.html
## Чем можно воспользоваться
С 2016 года [большинство][CIU] современных браузеров начали поддерживать
"пользовательские свойства", или, проще говоря, переменные в CSS. Это может звучать как
ультимативное решение, однако стоит учесть их ограничения: в CSS нет массивов, нет
логического типа данных, есть всего 4 базовые арифметические операции (стандартные
`+`, `-`, `*` и `/`), и, что самое главное - куда сложнее организовать подобие циклов.
[CIU]: https://caniuse.com/css-variables
Любой CSS с рекурсивным вычислением переменных в одной области видимости автоматически
перестаёт вычислять их значения, так что придётся хитрить. Первая идея - использовать
рекурсивно вложенные блоки. Так как CSS - это *каскадные* таблицы стилей, значение
переменной, вычисленной в конкретном блоке, доступно только в нём и его потомках. Но его
можно переопределить в любом подблоке, как в более локальной области видимости, по
аналогии с обычными языками программирования.
Саму логику вычислений можно организовать, используя числа 1 и 0 и логические операторы
из арифметических операций. Я нашёл целую [статью][CL] про то как их можно собрать и
применить на практике для 3D на чистом CSS.
[CL]: https://css-tricks.com/logical-operations-with-css-variables
Формула правила 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)`.
Итоговая формула в арифметической записи:
``` CSS
1 - (
1 - (q * (1 - p))
) * (
1 - (q + r - 2 * q * r)
)
```
## Первая реализация
[Висит тут][v0]. [Исходник на Jinja][vs0].
Ввод данных работает так же, как и в других примерах - на чекбоксах в первой строке.
Во избежание циклических формул каждая последующая строка находится на 2 уровня
вложенности глубже, чем предыдущая: на первом значение из переменных `v` дублируется в
`b`, а на втором значение более локальных `v` вычисляется по формуле из `b`. Итоговое
количество переменных - по две на столбец, с нумерацией с нуля: `--v0` и `--b0`, `--v1` и
`--b1` и так далее.
Вывод данных осуществляется при помощи классов `ci`, которые меняют цвет по формуле
`hsl(0, 0%, calc(100% - 100% * var(--vi)))`, становясь чёрными, если соответствующая
переменная `--vi` равняется единице.
После небольшой доработки декоративной части пример заработал... первые 4 шага.
![Rule110 v0][img_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 новой формулой][v1], [её исходник][vs1]. Оптимизация формулы добавляет по ещё
одному шагу как в Chromium, так и в Firefox, что позволяет в полной мере раскрыть
потенциал правила и вывести амогуса.
![Rule110 v1][img_v1]
*sus.*
Любопытному читателю предлагается (в интересах автора, конечно же) попробовать
оптимизировать формулу ещё сильнее или доказать невозможность выразить её записью менее
чем с 5 переменными.
Второй идеей для оптимизации было попробовать убрать промежуточные переменные `--bi`, для
чего я убрал вложенность и сделал отдельную переменную для каждой ячейки, но как оказалось
это ни на что не влияет - как не ухудшает ситуацию из-за 100 отдельных переменных в CSS
вместо 20, так и не улучшает из-за отсутствия дублирования значений и меньшей вложенности.
В любом случае посмотреть на попытку можно [тут][v2], [исходник][vs2].
![Rule110 full result][img_v2]
*Последняя версия в Firefox*
## Можно ли это применить?
В виде машин Тьюринга - вряд ли, но на одном CSS можно делать очень даже интерактивные
игры, я нашёл [целый сборник][CG] таких. Из самого примечательного в ней есть полностью
рабочий [сапёр][MS], хоть и с неудобным управлением, и ["платформер"][PG] с персонажем в
виде мышки.
![Minesweeper][img_ms]
[CG]: https://codepen.io/collection/AKkZro?cursor=ZD0wJm89MCZwPTEmdj00
[MS]: https://codepen.io/bali_balo/pen/BLJONZ
[PG]: https://codepen.io/nathantaylor/pen/KaLvXw
## Не пригодившееся
Помимо статьи про логические операторы, я нашёл и [статью][CO] про более сложные
математические функции (для реализации 3D): модуль, [сигнум][SGN], округление и остаток
от деления, но все они построены на экспериментальном правиле `@property`, которое пока
что [поддерживается не всеми браузерами][CIU_P].
[CO]: https://css-tricks.com/logical-operations-with-css-variables/
[SGN]: https://ru.wikipedia.org/wiki/Sgn
[CIU_P]: https://caniuse.com/mdn-css_at-rules_property
{% set dl = "/articles/turing_complete_css" %}
{% set rl = "/raw/content" + dl %}
{% set sl = "/source/content" + dl %}
[img_rules]: {{rl}}/rule110.svg
[img_v0]: {{rl}}/v0.png
[img_v1]: {{rl}}/v1.png
[img_v2]: {{rl}}/v2.png
[img_ms]: {{rl}}/ms.png
[v0]: {{dl}}/v0.j2
[v1]: {{dl}}/v1.j2
[v2]: {{dl}}/v2.j2
[vs0]: {{sl}}/v0.j2
[vs1]: {{sl}}/v1.j2
[vs2]: {{sl}}/v2.j2
{%- endfilter -%}
{%- endblock -%}