Как я запустил Doom в сообщении Telegram и уместил кадр в 1024 байта

Я попробовал сыграть в Doom прямо в сообщении Telegram. Движок оставил на своём Mac, а в чат отправил экран и шесть кнопок. Нажимаю «Вправо», и в том же сообщении появляется следующий кадр. На весь ответ у бота есть 1024 байта. Открыть ангар в Telegram.
У программистов есть давняя забава: проверить, где ещё запустится Doom. В doom-ascii ангар можно разглядеть прямо в терминале: стены, пол и противники нарисованы символами. Автор DoomPDF пошёл другим путём и уместил игру в PDF. Документ можно открыть в браузере и играть прямо на странице.
В Telegram ограничение оказалось другим: сколько от ангара останется видно, если на саму картинку отвести меньше килобайта? На скриншоте ниже можно разглядеть дверь и вспышку выстрела.

Что происходит за одно нажатие
В сообщении есть картинка и шесть игровых кнопок. Можно шагнуть вперёд или назад, повернуться, выстрелить или открыть дверь. Когда игрок поворачивает вправо, виджет вызывает 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% здоровья, счётчик показал шесть убитых противников.

Для запуска у себя понадобятся Python 3.12, токен своего бота и оригинальный DOOM1.WAD из Doom Shareware 1.9. Ресурсы игры в репозиторий не входят. Путь к WAD задаётся через DOOM_WAD. Отдельный игровой бот держит до шести сессий и освобождает их после десяти минут бездействия. Инструкция по запуску · ViZDoom · Документация Telegram по виджетам.
Что ещё можно запустить в сообщении
Для Doom нужен отдельный процесс с движком. Калькулятору хватает кнопок и арифметики в самом виджете. Мне захотелось собрать рядом такие разные примеры, чтобы можно было сначала попробовать виджет, а потом открыть исходники и посмотреть, как он устроен.
Так появился Awesome Telegram Widgets. В @awesomewidget_bot можно запустить Doom, пошаговый Pacman, калькулятор, демопрогноз погоды, выбор маршрута и демопочту. В разделе «Ещё виджеты» есть категории и поиск. У каждой записи есть ссылка на оригинальный проект. Если сделали свой виджет, предложите его в каталог и напишите, что в нём стоит попробовать.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.