Bollywood HungamaRamayana producer Namit Malhotra calls Los Angeles premiere his “Oscar moment” ahead of November 5 releaseEl ComercioLa hoja de ruta de Estados Unidos para Venezuela: reformas, vacancia de Delcy Rodríguez y eleccionesPunchBREAKING: NELFUND introduces token system to stop double payment of school feesThe Jerusalem PostQatari mediators present peace deal to Iran in mediation effort, official saysInquirer EntertainmentDonny Pangilinan felt ‘awkward’ over Showtime Baby Doll Juby’s dream about himDaily MaverickWildlife is not livestock — Aucamp’s dangerous plan to turn conservation into agricultureInquirerMimaropa drug bust in Palawan yields ₱51,884 worth of ‘shabu’ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioSky TG24Meteo, freddo e maltempo in Italia nel weekend di Halloween e Ognissanti. Le previsioniVariety‘One Cut of the Dead’ Director Ueda Shinichiro on Turning a Microdrama Into Tokyo Feature ‘Earthlings in Love’NumeramaÀ mi chemin entre le VTC et le longtail, le vélo électrique Nakamura Crosscity+ perd 500 €DeadlineRussell Brand Forcefully Denies Charges As He Gives Evidence During Rape Trial
The Daily Newsstand · Free, Always
Wednesday, October 28, 2026

Пошаговая стратегия в духе 90-х в одном HTML‑файле: Telegram Mini App, бэкенд за $0 и графика из нейросети

Translate

Помните игры, где герой с армией бродит по карте, вербует отряды в городах, осаждает замки и собирает по кусочкам карту следующей земли? Мы сделали такую игру заново и запустили её прямо внутри Telegram. Её не нужно ставить и не нужно регистрироваться: нажал кнопку в боте — и через пару секунд ты уже на карте.

Игра называется «Морвейн». Под капотом — один HTML‑файл без фреймворков, отрисовка на canvas, процедурные миры, тактический бой на 80 клетках и сервер на Cloudflare, который пока не стоил нам ни копейки. А всю графику — от травы до драконов — мы сгенерировали нейросетью и научили код собирать её в цельную карту.

В этой статье расскажу, что умеет игра, как она устроена и какие задачи пришлось решать по дороге. Будет про генерацию континентов, сохранения в облаке Telegram с лимитом 4 КБ на значение, честный рейтинг без античита на сервере, вырезание розового фона «по оттенку» и про то, почему преследующий вас слон должен быть опасен.

Что это за игра

Это пошаговая стратегия с RPG‑элементами: нужно покорить пять земель — от зелёного Морвейна через пустыни Сарнага, заснеженный Хрустальный Бор и Пепелище до мрачного Тёмного Удела. Вдохновение — классика 90-х: кто играл в King's Bounty, узнает дух сразу, но механики мы переделали под короткие сессии на телефоне.

Карта мира. Каждая земля — 80×60 клеток, сгенерированных заново в каждой игре: леса, горы, болота, пустыни, дороги и мосты. На ней пять городов, семь замков лордов и престол Хозяина Земли, пещеры, лагеря наёмников, пирамиды, сундуки и свитки. Всё скрыто туманом войны. Путь загораживают леса и скалы — их расчищают нанятые лесорубы, камнетёсы и плотники. А по карте бродят отряды монстров, которые замечают героя и бросаются в погоню.

Бой. Поле 10×8 клеток с деревьями, камнями и стенами при осаде. У каждого отряда своя скорость и очередь хода. Стрелки бьют издалека, но теряют точность за укрытием и не могут стрелять, когда враг вплотную. Копейщики особенно опасны для всадников и крупных зверей. Магия растёт вместе с героем: огненный шар, исцеление, воскрешение, ускорение, теневые копии отряда, смерч. Из боя можно отступить, но чем быстрее враг, тем больше потерь при бегстве.

Армия и герои. 32 вида существ в пяти фракциях — люди, природа, звери, нежить и чудовища, от гоблина до дракона. Три героя с разным стилем игры: рыцарь, чародейка и следопыт. Армию ограничивает лидерство: нанять сверх него можно, но в начале боя часть войск может взбунтоваться и перейти к врагу. Раз в неделю войска требуют жалованья, и неоплаченные бойцы просто уходят.

Прогресс. Захваченные замки открывают части карты пути в следующую землю, а последнюю часть хранит Хозяин Земли. Каждая следующая земля богаче, но и сильнее: на Тёмном Уделе врагов в 3,4 раза больше, чем на Морвейне. В городах — лавки заклинаний и снаряжения, гильдии работников и поручения.

Режимы. Обычный — с сохранениями каждое утро. Жёсткий — игра сохраняется после каждого действия, и поражение необратимо. И «Испытание недели»: раз в неделю все игроки получают одинаковую карту и героя и соревнуются, кто быстрее покорит первую землю. Достижения, звания, карточки «Поделиться» и приглашения друзей с бонусом обоим — тоже есть.

Стек: один файл и бесплатный сервер

Главное архитектурное решение — вся игра в одном index.html: HTML, CSS, JavaScript и все картинки в WebP (base64) внутри, около 5,5 МБ. Ни фреймворков, ни сборщика, ни npm. Карта, бой, генерация мира и ИИ противников работают на телефоне игрока, без сервера. Один файл — это ещё и атомарный релиз: не бывает ситуации, когда новый код загрузился, а старые картинки взялись из кеша.

Почему так:

  • Хостинг — GitHub Pages. Релиз — это git push. Файл кешируется, повторный запуск мгновенный.

  • Telegram Mini App. Игра открывается из бота в один тап. Telegram даёт имя игрока, облачное хранилище для сохранений, кнопку «Назад», «Поделиться» и платежи в звёздах. Вне Telegram тот же файл работает в обычном браузере — просто с локальными сохранениями.

  • Отрисовка на canvas. Карта собирается слоями: земля и плавные переходы, обрывы гор, деревья и объекты с сортировкой по глубине, туман войны, погода. Готовый кадр мира хранится во внеэкранном холсте: пока герой идёт, каждый кадр — это сдвиг готовой картинки и фигурка героя, а блики на воде дорисовываются поверх примерно 7 раз в секунду. Полная перерисовка — только когда мир изменился. Плотность пикселей ограничена ×2, а частицы можно выключить для слабых телефонов.

  • Сервер — Cloudflare Workers + D1 (SQLite). Он нужен только для общего: статистики, таблиц рекордов, «посылок» с наградами и ответов бота (вебхук с командами /play, /top, /invite и другими). Каждый запрос подписан Telegram (initData, HMAC‑SHA256 от токена бота), поддельные сервер отклоняет, а идентификатор игрока вычисляет сам. Токен бота живёт только в секретах Cloudflare. Бесплатного тарифа — 100 000 запросов в сутки — хватает примерно на 4000 активных игроков в день.

  • Аналитика пачками. Именно она делает лимит достаточным: события копятся в очереди и уходят одним запросом раз в 2 минуты или при накоплении 40 штук. Когда игру сворачивают, остаток улетает через sendBeacon, который браузер досылает даже после закрытия страницы.

  • «Посылки» вместо прямых начислений. Всё, что игрок получает от сервера (бонус за приглашение друга, подарок, в будущем покупка), лежит в одной таблице, и игра забирает это сама. Выдачу решает сервер, забрать дважды или чужое нельзя, а новые виды наград не требуют обновлять код сервера.

Самое интересное под капотом

Мир, который не запирает игрока

Рельеф строится из фрактального шума с порогами: выше одного — горы, ниже другого — вода, между ними леса, болота и пустыни. Характер земли задаётся пятью числами в описании земли, без отдельного кода: у Сарнага порог пустыни высокий, и половина карты уходит в песок, а Хрустальный Бор с низким порогом леса зарастает тайгой.

Сложнее всего было не сделать карту красивой, а сделать её честной. После генерации игра проверяет связность и считает для каждого сундука и замка, сколько леса и скал придётся расчистить, чтобы до него добраться. Это обычный поиск кратчайшего пути, где цена клетки — не шаг, а «сколько работы здесь нужно нанять». В первых версиях сундук мог стоять за двумя клетками леса и четырьмя горами — игроки правильно считали это издевательством.

Земли генерируются лениво, при первом визите. Для «Испытания недели» генерация идёт от зерна недели, поэтому у всех игроков карта совпадает до клетки.

Погоня, которая думает

Бродячие отряды не ищут путь каждый по отдельности. Один раз за такт строится поле расстояний от героя — поиск в ширину (BFS) по восьми направлениям, — и все преследователи просто «скатываются» по нему к меньшим значениям, обходя леса и скалы. Десять монстров стоят столько же, сколько один: 4800 клеток обходятся один раз. Срезать угол скалы по диагонали нельзя, а скорость отряда равна скорости его самого медленного существа.

Решение гнаться зависит от соотношения сил, и считается оно только по активным отрядам, без резерва: равные и сильные гонятся, слабые бьют только вплотную, а совсем слабые просятся к вам в армию за плату. Враги ходят, пока идёт герой, и могут перехватить его на полпути; ночью они подтягиваются ещё на несколько шагов.

Отдельная история — бегство. В ранних версиях догнавший слон показывал окно «Атаковать / Уйти», игрок нажимал «Уйти» — и слон просто останавливался. Вся погоня теряла смысл. Теперь от догнавшего врага можно уйти только с потерями: каждый отряд теряет 20% бойцов (40% в жёстком режиме), умноженные на отношение скорости самого быстрого преследователя к скорости отряда (от 0,5 до 2). Пехота, уходящая от волков, платит вдвое больше кавалерии. После бегства преследователь сутки не гонится, чтобы не зажать игрока в петлю. Добровольно отойти от логова, к которому сам подошёл, по‑прежнему можно бесплатно.

Бой: укрытия, бунт и простой, но честный ИИ

Укрытие. Когда стрелок стреляет, игра берёт 24 точки на отрезке от стрелка до цели и проверяет, не попадает ли какая‑нибудь из них в клетку с деревом, камнем или стеной. Если попадает, урон падает на 30%. Семплинг проще алгоритма Брезенхэма и на поле 10×8 с запасом точен. В журнале боя игрок видит приписку «укрытие -30%» и понимает, почему выстрел слабый.

Бунт. Лидерство — мягкий лимит: перебрать можно, но тогда перед боем игра честно показывает шанс бунта и то, кто именно уйдёт. Шанс равен двойной доле превышения (от 10 до 95%), рыцарь срезает его на 15 пунктов, а переходят к врагу в первую очередь самые «дорогие» по лидерству существа. Взять с собой лишнего дракона — риск драться с ним же.

ИИ противника намеренно прост и предсказуем: вплотную — бьёт, стрелок — стреляет в ближайшего, иначе подходит и бьёт любую достижимую цель, а если не дотягивается — идёт по полю расстояний в обход препятствий. Израненный отряд (меньше 30% здоровья) может сдаться панике и убежать за край поля — игрок видит, что он сбежал, а не погиб. Драконы не бегут никогда. Хитрый ИИ на маленьком поле быстро начинает раздражать, а простой позволяет игроку планировать.

Честный рейтинг без античита на сервере

В «Испытании недели» все играют на одной карте, и сравнивать результаты честно можно, только если удача тоже одинакова. Поэтому вся случайность, влияющая на игру, идёт через одну функцию R(). В обычной игре это Math.random, а в испытании — генератор mulberry32, засеянный FNV‑хешем строки «зерно недели | ключ потока». Потоков много: свой на каждый день и свой на каждый бой (клетка боя + номер попытки). Это важно: если бы поток был один, лишний шаг по карте сдвигал бы все броски в будущих боях, и два игрока с одинаковой тактикой в бою получали бы разные исходы. Номер недели считается по ISO в UTC одной и той же функцией на клиенте и на сервере.

Та же идея закрыла классическую лазейку жёсткого режима «закрыть игру, пока бой не проигран». В жёстком режиме каждый бой получает случайное зерно, которое сохраняется в момент начала боя. Перезапуск вернёт вас в тот же бой с той же удачей — и человека с разрядившимся телефоном это не накажет, и читеру не поможет. Сервер при этом игру не симулирует: он проверяет подпись Telegram и правдоподобие результата. Для инди‑игры без денег на кону это разумный компромисс.

Сохранения в облаке Telegram по 4 КБ

Telegram даёт каждому Mini App облачное хранилище, привязанное к аккаунту: сохранение переживает смену телефона. Но одно значение там — не больше 4096 символов, а в игре пять карт по 4800 клеток с туманом войны. Решение в три шага:

  1. Сжатие. Рельеф и туман записываются RLE‑строкой: карта в 4800 клеток превращается в 1–2 тысячи символов. Всё остальное — обычный JSON.

  2. Нарезка. Строка режется на куски по 4000 символов, которые пишутся параллельно.

  3. Атомарность. Куски пишутся в чередующиеся слоты A и B, а мета‑запись «актуален слот B, в нём N кусков» — строго последней. Если игру закрыли на полпути, мета ещё указывает на старый целый слот, и сохранение не портится.

Параллельно та же строка ложится в localStorage как быстрый кеш, а при загрузке берётся более свежая из двух копий. Снимок состояния делается один раз, в момент сохранения, и уже готовой строкой уходит в оба места. Иначе медленная запись в облако могла бы захватить более позднее состояние, чем локальная, и две копии разошлись бы.

Графика из нейросети, собранная кодом

Сначала вся карта рисовалась процедурно — кружками и многоугольниками. Потом мы начали переводить её на сгенерированные картинки: текстуры земли, деревья, города, замки, шапки окон, фигуры всех 32 существ. Одна земля — это около 30 картинок. Чтобы они сложились в одну картину, у всех промтов общий блок стиля с ракурсом «сверху под 60°, свет слева сверху» и строка палитры земли. Картинки проходят через Python‑конвейер (Pillow + NumPy) и вшиваются в файл игры как WebP. Дальше начинается инженерия.

Прозрачность. Генераторы не умеют настоящий альфа‑канал и рисуют «шахматку» прямо на картинке. Поэтому все объекты генерируются на ярко‑розовом фоне #FF00FF, а вырезаем мы его не по точному цвету, а по оттенку — углу в плоскости (R‑G, B‑G). Это даёт три вещи сразу:

  • фон любой яркости уходит целиком, включая градиенты, которые генератор любит добавлять «для красоты»;

  • тёмно‑фиолетовые тени под объектами (фон, умноженный на тень) превращаются в полупрозрачные чёрные, и дерево ложится со своей тенью на любую траву;

  • фиолетовая мантия чародейки, у которой синего заметно больше, чем красного, остаётся нетронутой.

На кромке розовая примесь «размешивается»: из цвета пикселя вычитается доля фона, и она же становится прозрачностью. Поэтому дымные хвосты призраков и сияние магических кристаллов остаются полупрозрачными и не получают розового ореола.

Бесшовные текстуры. Текстуру сдвигаем на полкадра, чтобы шов оказался в центре, и смешиваем с оригиналом с весом, который растёт к краям. Отдельно выравниваем низкие частоты яркости: вычитаем сильно размытую копию и добавляем среднее. Иначе тёмное пятно в центре сгенерированной воды повторялось бы в каждой клетке, и озеро стало бы клетчатым.

Переходы между типами земли. Чтобы трава, песок и болото не стыковались квадратами, для каждого типа строится маска прозрачности: 8 точек на клетку, тройное скользящее среднее (дешёвое приближение гауссова размытия) и фрактальный шум только в полосе перехода. Кромка получается неровной и мягкой, а сетка клеток перестаёт читаться. Горы рисуются плато с угловатой кромкой, отвесной стеной из текстуры утёса и осыпью под ней. Кривая контраста кромки берётся из заранее построенной таблицы на 256 значений, внутри циклов нет Math.min/Math.max, а шум кешируется на землю. Это ускорило пересборку масок примерно вдвое — важно, когда лесоруб вырубает рощу, и карта меняется.

Наборы земель. У каждой земли свой набор картинок под теми же именами. «Дуб» на Сарнаге — это пальма, «сосна» — акация, «замок» — крепость из песчаника. При переходе на другую землю игра просто подменяет словарь картинок, а чего нет в наборе земли, берёт из базового. Разная форма деревьев компенсируется множителем ширины: узкая пальма рисуется уже, раскидистая акация — шире. Так новая земля подключается без единой правки в коде отрисовки.

Экономика, в которой видно всё

Раз в неделю армия получает жалованье, резерв — за полцены. Не хватило денег — каждый отряд теряет ровно ту долю бойцов, которая не оплачена. Чтобы это не было неприятным сюрпризом, рядом с золотом в панели виден итог недели (+ или ‑), за два дня до выплаты приходит предупреждение, а окно «Казна» раскладывает доход и содержание по статьям, вплоть до каждого отряда. Уход неоплаченных бойцов стоит за флагом в коде: если аналитика покажет, что игроки страдают на первой земле, механику можно выключить одной строкой.

Тесты без тестового фреймворка

Раз игра — один файл, её удобно тестировать целиком: Playwright открывает его в Chromium, дёргает функции игры напрямую и снимает скриншоты. Сценарии проверяют бои, найм, лидерство, перехват на карте, бегство и продолжение боя после перезапуска, а скриншоты каждой земли в телефонном разрешении ловят обрезанные подписи и съехавшие окна. Производительность меряем с замедлением процессора в 4 раза через Chrome DevTools Protocol — именно так выяснилось, что полноэкранные градиенты «теней облаков» слишком дороги для слабых телефонов, и от них отказались везде, кроме небольшого тумана Тёмного Удела.

На каком этапе игра

Сейчас «Морвейн» — это версия 0.36, открытая альфа. Игра проходится от начала до конца: пять земель, три героя, 32 существа, три режима, достижения, еженедельный рейтинг и приглашения друзей. Игровая часть в целом собрана, и сейчас идёт настройка баланса по аналитике и отзывам игроков.

Самая большая текущая работа — переход на новую графику. Картинки готовы для двух земель из пяти — Морвейна и Сарнага, фигуры всех существ и шапки окон тоже готовы. Остальные три земли пока рисуются старым процедурным способом, и здесь пригодилась система наборов: каждая новая земля подключается целиком, когда её картинки готовы, и ничего не ломается по дороге.

Как это делается

Игра разрабатывается короткими итерациями. Каждая правка сначала попадает в прототип: отдельную копию игры, которую можно открыть по ссылке и потрогать с телефона. Только после проверки она переезжает в основную ветку. Релиз — это git push: GitHub Pages обновляет страницу, и игроки получают новую версию при следующем открытии бота. Старые сохранения при этом мигрируют на лету, чтобы никто не потерял прогресс.

Для отладки есть скрытый админ‑режим: с ним можно прыгать между землями, телепортироваться к любому типу объектов и запускать тестовые бои на любом грунте, не проходя полкарты. Он доступен только аккаунту разработчика, а пока он включён, достижения и аналитика отключены, чтобы тесты не портили статистику.

Что впереди

  • Остальные земли в новой графике. На очереди Хрустальный Бор, Пепелище и Тёмный Удел — по 30 картинок на каждую, со своими стенами, замками и деревьями.

  • Города, которые видно по карте. Уже есть подписи и значки специализации, дальше — разный вид самих городов, чтобы сразу было видно, где продаются свитки, а где броня.

  • Монетизация через Telegram Stars — только косметика и удобства, без покупки силы. Инфраструктура «посылок» для этого уже готова.

  • Английская версия и отдельное приложение для Android. Тот же файл в WebView, с локальными сохранениями вместо облака Telegram — код к этому уже готов.

Попробуйте

Игра бесплатная и запускается прямо в Telegram, без установки: @morveyn_game_bot. Дневник разработки, новости и обсуждение баланса — в канале @morveynRPG.

Буду рад вопросам в комментариях: про генерацию карт, про бэкенд за $0, про то, как заставить нейросеть рисовать в одном стиле тридцать разных объектов. И, конечно, про то, где игра сломалась у вас.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.