Предлагаю небольшой челлендж для тех, кто пишет на JavaScript или TypeScript.
В квизе — 10 коротких фрагментов кода из известных Open Source-проектов. В каждом PVS-Studio когда-то нашёл ошибку, но вам об этом заранее не скажут.
Задача простая: найти подозрительное место за 60 секунд. Успели — получаете балл. Не успели — что ж, возможно, статический анализатор оказался внимательнее 😄
На всё уйдёт около 15 минут. А в конце ещё обещают подарок.
Проверим, насколько хорошо вы умеете замечать ошибки, которые не бросаются в глаза?
Попался мне сервис VibeCraft, помощник в создании сайтов и веб-приложений без знания программирования. Набрасываешь ИИшке свою идею, обсуждаете её и она сама начинает его делать. Если результат тебя устроит то можно опубликовать через Яндекс Облако всему миру.
Встречающий интерфейс
Я решил придумать игру, которая хорошо подходит под такой формат разработки: технически достаточно компактную, без сложной физики, огромного мира, управляемого персонажа, pathfinding и других вещей, которые моментально раздувают небольшой проект, но при этом не совсем примитивную. Хотелось, чтобы одно прохождение занимало хотя бы полчаса, чтобы во время него был заметный прогресс и чтобы после победы оставалась причина начать заново.
Идею игры я стал придумывать с другой ИИ моделью. Мы перебрали несколько концепций, и в какой-то момент появилась идея выживания после кораблекрушения. Есть небольшая группа людей, оказавшихся на острове. Их нужно кормить, распределять по профессиям, добывать ресурсы, строить поселение, исследовать остров и в конечном итоге собрать корабль, чтобы выбраться.
После этого дизайн документ на 1600 строк отправился в Vibecraft и разработка игры началась :)
Первый вариант игры
Оказался довольно полноценным, при этом игра не развивалась по классическому сценарию «сначала две кнопки, потом постепенно добавлял механики». Основные системы были задуманы практически сразу: население, профессии, несколько видов ресурсов, здания, улучшения, экспедиции, случайные события и строительство корабля как финальная цель.
Игровой цикл получился довольно простой. Игрок распределяет людей по профессиям, те постоянно производят ресурсы. За ресурсы строятся здания, которые открывают новые профессии и возможности. Часть жителей можно отправлять в экспедиции, временно теряя их как рабочих, но получая взамен редкие ресурсы, бонусы или новых людей. Постепенно открывается верфь, после чего начинается строительство корабля. Когда он готов, можно покинуть остров и закончить прохождение.
То есть довольно быстро возникла ситуация, когда игра уже формально была игрой: её можно было запустить, развить поселение и победить. Казалось бы, осталось немного отполировать интерфейс и можно выпускать.
Первая версия с доработками
На практике именно в этот момент и началась большая часть работы.
Пока механика существует только в голове или в таблице, всё обычно выглядит логично. Когда начинаешь проходить собственную игру от начала до конца, внезапно обнаруживаются очень простые проблемы. Например, для строительства Лесного лагеря требовалось волокно, а стабильная добыча волокна открывалась как раз после строительства Лесного лагеря. Формально всё было реализовано правильно, ошибок в коде не было, интерфейс работал - просто пройти игру дальше было невозможно.
Таких случаев оказалось достаточно много. Где-то один ресурс накапливался слишком медленно, где-то наиболее выгодной стратегией становилось отправить почти всех жителей в одну профессию, где-то игрок открывал новую возможность, но не замечал этого, а где-то после случайного события было совершенно непонятно, стало поселению лучше или хуже. И в какой-то момент стало очевидно, что сделать систему работающей и сделать её нормально играющейся - довольно разные задачи.
После первого варианта я уже решил утащить проект из VibeCraft и с помощью ИИ агента доделывать его.
Интерфейс
Самая большая доработка, если смотреть на игру глазами разработчика, первый вариант интерфейса был совершенно нормальным. Есть блок с ресурсами, есть профессии, здания, улучшения, экспедиции и корабль. Все данные на экране присутствуют - вроде бы чего ещё хотеть?
Но чем больше становилось контента, тем сильнее интерфейс начинал напоминать панель управления каким-то складским комплексом. Игроку постоянно приходилось искать, где именно появилось новое доступное действие, почему сейчас нельзя построить нужное здание и что вообще лучше делать дальше.
В результате интерфейс пережил несколько серьёзных переделок. Основные вещи - население, ресурсы и текущая цель - остались постоянно видимыми, а остальные системы разъехались по отдельным вкладкам. Заблокированный контент начали явно отличать визуально, а для текущей цели появилась отдельная подсказка.
Причём первая версия этой подсказки была довольно забавной. Она выглядела умной, но на самом деле просто рекомендовала следующую доступную экспедицию. Это быстро стало очевидно во время тестирования. В следующей версии подсказка уже анализировала текущую цель и пыталась предложить действительно полезное действие. Если для строительства не хватает камня - найти способ получить камень. Если есть подходящая экспедиция, предложить её. Если нет - посоветовать назначить больше людей каменщиками.
Такие изменения кажутся мелочами по сравнению с добавлением новой механики, но именно после них игра начинает ощущаться заметно понятнее.
Механики
Экспедиции были в проекте с самого начала, но позже они заметно изменились. Изначально экспедиции были максимально простыми: отправил экспедицию, ждешь получения результата. Для первых минут игры этого хватало, но довольно быстро такие события стали слишком предсказуемыми.
Тогда у них появились разветвления. Теперь одно решение может привести к следующему этапу экспедиции, где игроку снова приходится выбирать. Например, можно сразу забрать небольшую гарантированную награду или рискнуть и продолжить исследование. Во втором случае результат может оказаться значительно лучше, а может закончиться потерей ресурсов, временным негативным эффектом или другой неприятностью.
Из простых модальных окон экспедиции постепенно превратились в небольшие истории. Некоторые ветки позволяют вовремя остановиться и сохранить уже найденное, другие специально провоцируют попробовать удачу ещё раз.
Можно было продолжать добавлять в игру новые системы, но гораздо полезнее оказалось сделать интереснее те, которые уже существуют.
Метапрогрессия
А вот разные острова как раз не входили в самый первый вариант игры. К их появлению постепенно подтолкнула метапрогрессия.
После прохождения игрок получает очки выживания, которые можно потратить на постоянные улучшения: увеличить производство, получить больше еды в начале следующего прохождения, расширить склад, сделать эффективнее рыбаков или строителей, улучшить экспедиции и так далее. Но довольно быстро возник очевидный вопрос: зачем становиться сильнее между прохождениями, если после этого игрок просто снова попадает на абсолютно тот же остров?
Так появились новые острова с разными условиями. Не хотелось делать под каждый из них отдельный набор механик, вместо этого они используют ту же экономику, но меняют её параметры. На одном острове сложнее добывать определённые ресурсы, на другом сильнее давление со стороны потребления еды, где-то дольше идёт строительство, а где-то чаще случаются неприятные события.
Получилось, что одна и та же игровая система начинает требовать немного другой стратегии без необходимости создавать ещё одну игру внутри игры. С точки зрения объёма разработки это оказалось довольно выгодным способом увеличить реиграбельность.
Графика
Отдельно постепенно развивалась визуальная часть. Сначала главной задачей было просто сделать интерфейс читаемым, но довольно быстро захотелось, чтобы происходящее ощущалось именно игрой, а не таблицей ресурсов с кнопками. Появились иконки профессий, ресурсов, событий, экспедиций и улучшений, отдельные изображения островов, а затем и сама сцена поселения. Графика эволюционировала от эмоджи до полноценных (сгенерированных, но всеже) иконок.
Когда сама игра уже была проходимой, начался отдельный этап подготовки к публикации в Яндекс Играх. SDK, реклама, rewarded-реклама, сохранения, локализация, требования платформы к поведению страницы, иконка, обложка, gameplay-видео, русская и английская версия оформления. Почти ничего из этого не делает непосредственно игровой процесс интереснее, но без этих вещей игра всё ещё остаётся проектом на компьютере разработчика, а не законченным продуктом.
Итоговая версия
Немного о том, как всё устроено внутри
Технически проект получился довольно обычным веб-приложением на React и TypeScript. Сейчас он собирается через Vite. При этом игровая логика старается не зависеть от интерфейса: есть общее состояние игры и игровой tick, который занимается производством и потреблением ресурсов, строительством, таймерами экспедиций, временными эффектами и другими процессами.
Большая часть контента хранится в виде конфигураций. Отдельно описаны профессии, здания, улучшения, события, экспедиции, острова и мета-улучшения. Благодаря этому добавить новое событие или изменить параметры острова можно без переписывания основной логики игры.
По мере развития проекта вокруг этого довольно простого ядра начало появляться всё больше обычной продуктовой инфраструктуры. Появились автосохранения, русская и английская локализация, интеграция с SDK Яндекс Игр, реклама, rewarded-реклама, unit-тесты, E2E-тесты через Playwright и даже отдельный скрипт для симуляции баланса.
Проект немного вырос из первоначальной идеи «сделаем что-нибудь компактное».
Отдельной технической историей оказался переход с Next.js на Vite. Изначально проект в VibeCraft использовал Next.js, и каких-то принципиальных проблем с ним не было. Просто формат публикации в Яндекс Играх довольно простой: нужно собрать готовые статические файлы, положить их в архив и загрузить на платформу. Внутри должен находиться `index.html`, JavaScript, стили и ассеты, причём желательно с максимально предсказуемыми относительными путями.
Для такого сценария Next.js оказался просто менее удобен, чем хотелось. Серверная часть игре была не нужна вообще, а задача фактически сводилась к получению максимально обычной папки со статическим билдом. После перехода на Vite процесс стал гораздо проще: собрал проект, получил готовую директорию, заархивировал и загрузил в Яндекс Игры.
Это не столько история о том, что один инструмент лучше другого, сколько обычный случай, когда более простой инструмент лучше соответствует конкретной задаче.
И немного про AI
Почти весь проект делался с активным использованием AI-инструментов, и после этой разработки у меня осталось довольно чёткое впечатление: AI действительно очень сильно снижает стоимость реализации.
Сделать новый экран, изменить компонент, добавить тест, переделать систему сохранений или реализовать небольшую механику сейчас можно заметно быстрее, чем раньше. Иногда достаточно нормально сформулировать задачу, и значительная часть рутинной работы исчезает.
Но есть обратная сторона. Сама необходимость принимать решения никуда не делась. Кто-то всё равно должен заметить, что игрок не видит доступное здание, понять причину и решить, что именно нужно поменять. Кто-то должен обнаружить тупик в прогрессии, заметить скучную ветку события или понять, что ещё одна новая механика игре уже не нужна.
Наверное, мой главный вывод после этого проекта именно такой: AI сильно удешевил написание кода, но из-за этого ещё важнее стало понимать, что именно стоит писать.
Добавить в игру ещё одну систему теперь очень легко. Иногда даже слишком легко. Поэтому одна из самых сложных задач постепенно становится не «как это реализовать», а «нужно ли это вообще реализовывать».
В итоге проект, который начинался как небольшой эксперимент для Vibecraft, довольно быстро превратился в самостоятельную браузерную игру.
Самым важным для меня было не количество сделанных механик, а сам факт того, что игру удалось довести до состояния, когда можно перестать добавлять фичи, собрать архив и наконец нажать кнопку публикации. И на все это потратилось меньше недели времени.
Здравствуйте, сеньоры, помидоры Есть 2 файла, я их соединяю в один, в заголовок файла на выходе нужно записать хэш соединенных данных, в конец нужно записать хэш вместе с заголовком, как это сделать через браузер без перечитывания исходных файлов, без буфера в память или лишней копии на диске, перечитать финальный файл можно, то есть чтобы кроме финального файла больше ничего не создавать?
Кажется не сложно, надо просто перечитать соединённые данные для хэша, на лету посчитать без буфера нельзя так как сначала пишем середину, а потом надо вернуться записать заголовок, браузер не может открыть на чтение файл который открыт на запись, но если открыть перечитать, а потом открыть дописать он делает копию на диске.
Решил я как-то, что моему боту TG Notion пора заговорить по-английски. Казалось бы — чего сложного? Перевёл строки, добавил кнопку, готово.
Ага, щас.
В итоге я три дня чинил то, что не должно было ломаться, и чуть не поседел. Но по порядку.
Первый затык — кнопка
Сначала сделал просто: в шапку добавил кнопку EN/RU. Нажал — язык поменялся. Красота.
Но когда перезапустил Mini App — текст кнопки сбросился на EN, хотя интерфейс остался английским. То есть язык поменялся, а кнопка об этом забыла.
Оказалось, я забыл обновлять текст кнопки при загрузке. Мелочь, но полчаса жизни она мне сожрала.
javascript
Все строки через t()
Дальше встал вопрос: как переводить всё это хозяйство? Захардкодить английский в HTML? Нет, нужен был нормальный подход.
Сделал словарь translations для двух языков и функцию t(key), которая достаёт нужную строку. Все тексты в интерфейсе — только через t().
javascript
И всё, что видит пользователь, теперь идёт через t('...'). Кнопки, плейсхолдеры, ошибки, пустые состояния.
Бэкенд тоже надо переводить
Думал, что на этом всё. Хуй там.
Оказалось, что inline-карточки досок генерируются на сервере, а не в Mini App. И если в Mini App язык сменился, сервер об этом знать не знает. Поэтому карточки в чате оставались на русском.
Пришлось и на бэкенде делать переводы. Добавил getLang() и те же ключи.
javascript
Теперь кнопки в карточках зависят от языка. Но тут вылезла новая залупа. Синхронизация языка
Проблема: юзер в Mini App выбрал английский, а бэкенд смотрит только на язык Telegram. Если в Telegram русский, а в Mini App английский — карточки всё равно русские.
Решение — сохранять выбранный язык в базу. При переключении в Mini App отправляем язык на сервер, а бэкенд при генерации карточек берёт его из базы.
javascript
javascript
После этого язык в Mini App и в карточках наконец совпал.
Склонение — отдельная боль
Казалось бы, всё готово. Но остался пустяк: счётчик заметок.
«1 заметок», «3 заметок» — звучит как говно. Для русского нужны склонения: заметка, заметки, заметок. Для английского проще: note/notes.
javascript
Убил на это ещё час. Зато теперь «1 заметка», «2 заметки», «5 заметок» — как надо.
Итог
Локализация готова. Теперь TG Notion говорит на двух языках, сохраняет выбор и не путается в склонениях.
Но самое главное — я понял: перевод — это не «поменял строки», а целый слой архитектуры. Особенно когда у тебя фронт, бэкенд и база.
Если интересно посмотреть на код или пощупать бота: