Я попробовал сыграть в Doom прямо в сообщении Telegram. Движок оставил на своём Mac, а в чат отправил экран и шесть кнопок. Нажимаю «Вправо», и в том же сообщении появляется следующий кадр. На весь ответ у бота есть 1024 байта. Открыть ангар в Telegram.

У программистов есть давняя забава: проверить, где ещё запустится Doom. В doom-ascii ангар можно разглядеть прямо в терминале: стены, пол и противники нарисованы символами. Автор DoomPDF пошёл другим путём и уместил игру в PDF. Документ можно открыть в браузере и играть прямо на странице.

В Telegram ограничение оказалось другим: сколько от ангара останется видно, если на саму картинку отвести меньше килобайта? На скриншоте ниже можно разглядеть дверь и вспышку выстрела.

Выстрел в ангаре. Скриншот из Telegram для macOS
Выстрел в ангаре. Скриншот из Telegram для macOS

Что происходит за одно нажатие

В сообщении есть картинка и шесть игровых кнопок. Можно шагнуть вперёд или назад, повернуться, выстрелить или открыть дверь. Когда игрок поворачивает вправо, виджет вызывает TelegramWidget.request("right"). Бот находит игровую сессию и передаёт в ViZDoom кнопку поворота. Движок продвигает симуляцию на пять тиков. За это время поворачивается игрок, движутся противники, летят снаряды. Затем мир снова замирает.

Бот берёт кадр из экранного буфера и возвращает его через answerWidgetQuery. Виджет меняет источник картинки в том же элементе img. Поэтому после десятка ходов в переписке по-прежнему одно игровое сообщение. Пока ответ в пути, виджет игнорирует новые нажатия. Между запросами проходит как минимум 1010 мс. Сетевую задержку при повороте видно сразу, зато за время ожидания монстр не подойдёт ближе.

Сколько картинки помещается в килобайт

ViZDoom отдаёт RGB-кадр размером 320 × 200. Это 192 000 байт до сжатия. На результат запроса есть 1024 байта, причём в них должна поместиться строка Base64. Каждые три байта изображения превращаются в четыре символа. Значит, на сам WebP остаётся максимум 768 байт. Если сжать картинку ровно до килобайта, она уже не пройдёт.

Я оставил качество WebP постоянным и подбираю разрешение для каждого кадра. Сначала пробую ширину 120 пикселей. Если строка слишком длинная, пробую 96, затем 80, 64, 48 и 32. Высоту меняю пропорционально. Так пустая стена и зал с противником могут прийти с разным разрешением. Проверку делаю после кодирования, на той строке, которую получит Telegram.

for width in (120, 96, 80, 64, 48, 32):
    buf = io.BytesIO()
    image.resize((width, width*5//8), Image.Resampling.LANCZOS).save(
        buf, "WEBP", quality=25, method=3
    )
    data = base64.b64encode(buf.getvalue()).decode("ascii")
    if len(data) <= 1024:
        return data

На маленькой картинке трудно разглядеть дальнего противника. Зато можно открыть дверь и посмотреть, кто за ней. Все столкновения, двери и попадания считает движок. Исходный код обработчика кадров.

Можно ли дойти до выхода

В Telegram для macOS я проверил путь от стартового зала до двери, поворот и выстрел. Весь уровень проверил отдельно, в локальном ViZDoom, с теми же шестью кнопками и пятью тиками на ход. До выходного выключателя понадобилось 537 ходов. На финише осталось 48% здоровья, счётчик показал шесть убитых противников.

Перестрелка у токсичного бассейна. Локальный кадр ViZDoom до сжатия для Telegram
Перестрелка у токсичного бассейна. Локальный кадр ViZDoom до сжатия для Telegram

Для запуска у себя понадобятся Python 3.12, токен своего бота и оригинальный DOOM1.WAD из Doom Shareware 1.9. Ресурсы игры в репозиторий не входят. Путь к WAD задаётся через DOOM_WAD. Отдельный игровой бот держит до шести сессий и освобождает их после десяти минут бездействия. Инструкция по запуску · ViZDoom · Документация Telegram по виджетам.

Что ещё можно запустить в сообщении

Для Doom нужен отдельный процесс с движком. Калькулятору хватает кнопок и арифметики в самом виджете. Мне захотелось собрать рядом такие разные примеры, чтобы можно было сначала попробовать виджет, а потом открыть исходники и посмотреть, как он устроен.

Так появился Awesome Telegram Widgets. В @awesomewidget_bot можно запустить Doom, пошаговый Pacman, калькулятор, демопрогноз погоды, выбор маршрута и демопочту. В разделе «Ещё виджеты» есть категории и поиск. У каждой записи есть ссылка на оригинальный проект. Если сделали свой виджет, предложите его в каталог и напишите, что в нём стоит попробовать.