История проекта

1. Искра (2013)

Летом 2013 года, ещё учась в школе, я наткнулся на статью об Альтеративных Крестиках-Ноликах. И я влюбился в эту игру мгновенно:

  • Она предлагала небольшой набор простых правил, дающих огромную сложность, подобно «Игре жизни» Джона Конвея.
  • Она требовала глубокого стратегического мышления, но без жёсткого багажа шахматных фигур.

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

2. Видение: от бумаги к коду (2016)

Позже, в университете, я хотел играть с друзьями из разных городов. Не найдя качественных онлайн-версий, решил сделать свою.

С детства я одержим обобщением: изучением или разработкой универсальных теорий, описывающих все существенные детали и охватывающих максимум практики, будь то физика или игровая логика. Поэтому мне доставило невероятное удовлетворение обобщить игровой движок для работы с произвольным набором параметров:

  • S (Длина стороны блока): выход за рамки стандарта 3×3, включая уменьшенные, динамичные 2×2 (где ничья невозможна) и увеличенные 4×4 или 5×5.
  • N (Глубина вложенности): поддержка классических крестиков-ноликов, Super/Meta/Ultimate Tic-Tac-Toe и даже более глубоких фрактальных уровней.
  • Несколько команд: поддержка более чем двух игроков (X и O); хотя поначалу это не казалось достаточно увлекательным.

Например, классическая игра XO представляется как S3 N1 в моём формате, а Ultimate Tic-Tac-Toe – как S3 N2, и есть множество других конфигураций!

Я погрузился в игровой ИИ, реализовав алгоритм Minimax с альфа-бета отсечением и специализированной эвристической оценкой, способной адаптироваться к различным конфигурациям полей.

3. Начальная разработка

Изначально я исследовал C++/Qt для кросс-платформенности. Однако, увидев результат на смартфоне, я остался недоволен – UI выглядел неуклюже, он не использовал нативные элементы. Я отказался выпускать «некрасивый» продукт. Я мог бы интегрировать нативный код для доступа к элементам интерфейса ОС, но это казалось сшиванием монстра Франкенштейна.

Я решил пойти полностью нативным путём: отдельные реализации на Objective-C для iOS и Java для Android. Это позволило создать отполированный, платформенно-специфичный опыт, несмотря на параллельные кодовые базы. Для бэкенда я построил сервер на Python с использованием асинхронного фреймворка Tornado (до того как async/await стал стандартом). Я даже реализовал двойной API с фолбэком: игра пытается установить WebSocket-соединение для скорости, но переключается на HTTP, если Wi-Fi-сеть блокирует протокол WS; разумеется, всё через SSL/TLS. Это был мой первый опыт с бэкенд-разработкой и взаимодействием с Linux. Я также разработал клиентскую браузерную версию, хотя она не поддерживала мультиплеер.

Я также посвятил время брендингу – созданию названия и иконки. Хотел нечто простое, но уникальное, запоминающееся и явно связанное с наследием «X и O». В итоге я пришёл к дизайну, который, как мне кажется, по-настоящему резонирует с идентичностью игры.

4. Реальность рынка

Чтобы сделать игру глобальной, я работал с друзьями над переводом на восемь языков. Но быть студентом-разработчиком дорого. Между ежегодной платой Apple Developer в $100 и стоимостью серверов я добавил рекламу, чтобы игра окупала себя. Я даже вложил дополнительные $100 в рекламу для привлечения аудитории – главная причина, по которой игра набрала тысячи установок на Android и сотни на iOS.

Однако финансовая реальность оказалась холодным душем: я заработал всего $12 за год – даже недостаточно для достижения порога вывода средств! ROI (возврат инвестиций) составил фактически -90%… Парадоксально (хотя, возможно, неудивительно), при том что на Android было больше пользователей, он принёс лишь ~$1.50, а остальное – с платформы Apple. По крайней мере, через внутриигровой чат я познакомился со множеством интересных людей со всего мира!

Из-за отсутствия маркетинговых знаний (я ещё не слышал о Reddit или Discord) и постоянного обслуживания, требуемого меняющимися политиками Google и Apple, оригинальные мобильные версии в итоге исчезли из App Store и Play Market.

5. Веб-перерождение (2022)

Весной 2022 года я решил создать единую полнофункциональную браузерную версию игры с использованием современных технологий:

  • новый сервер с async-native FastAPI, сохраняющий практически тот же API.
  • новую веб-версию TacticToy на React.js, которую гораздо проще разрабатывать благодаря модульности (версия на vanilla JS была одним файлом с 2000 строк!), но с поддержкой мультиплеера через WS/HTTP, как в старых мобильных версиях. Она всё ещё доступна: v2.TacticToy.com

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

6. Универсальный движок TT на Rust

Имея опыт с различными языками программирования и технологиями (даже больше, чем упомянуто здесь) и страсть к разработке разнообразных проектов, я устал от необходимости поддерживать разные базы знаний для работы над ними. Поэтому годами я искал «единственное кольцо» – технологию, способную обеспечить высокую производительность и истинную кросс-платформенность. Я присматривался к Dart/Flutter, React Native, GoLang и т.д., иногда экспериментировал, но они меня не устраивали. Напротив, чем больше я изучал Rust, тем больше он мне нравился. Решил начать активно изучать его и использовать для проектов в июле 2023.

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

  • Помимо анимаций и звуковых эффектов, я планировал разделить длину стороны блока и длину выигрышной линии (концепция, известная как m,n,k-игра или k-в-ряд). Теперь это параметр L.
  • Это также помогает гораздо лучше поддерживать несколько команд!
  • Параметр S больше не ограничен 5, он может быть до 16. Вместо этого есть ограничения на общую длину стороны поля (S^N < 100) и объём (S ^ (N*D) < 1000).
  • Я подумал, что мой алгоритм легко обобщить для 3D и даже 4-мерных полей, и это слишком интересно, чтобы не сделать! Это, конечно, параметр D.
  • Хотя алгоритм Minimax надёжен и довольно эффективен с альфа-бета отсечением, он сталкивается с взрывным ростом пространства состояний на больших полях, что недопустимо с учётом вышеуказанных изменений. Поэтому я думал о переходе к алгоритму Monte Carlo Tree Search (MCTS) для лучшего контроля времени «размышления» ботов.
    • Почему он лучше подходит? Minimax – детерминированный алгоритм, он пытается увидеть «конец света» (хотя бы частично), а MCTS – вероятностный: он исследует наиболее перспективные сценарии без полного перебора, что делает его единственным жизнеспособным выбором для управления взрывным ростом пространства состояний.
  • Я знал, что ультимативные версии К0 различаются в обработке уже выигранных блоков: одни позволяют отправлять и ходить в них, другие запрещают. Я хотел дать игрокам выбор стиля.
  • Условие победы: поддержка «Контроля территории» (победа по большинству блоков) помимо классической линии из трёх блоков.
  • Контроль времени: важная механика для мультиплеера, делающая игру настоящим соревнованием! Игра должна позволять выбирать предпочтительный лимит времени, будь то 10 секунд или 10 часов, а также желаемое наказание за просрочку: случайный ход или мгновенная дисквалификация.
  • Мультиплеер оригинальной версии TT позволял иметь нескольких игроков в одной команде, но они должны были ходить в порядке очереди, а нескольким людям зачастую сложно скоординироваться, поэтому я хотел позволить любому члену команды действовать во время хода команды.
  • Улучшенная интернационализация (i18n): я решил добавить более 20 самых распространённых языков и даже несколько искусственных! Это было непросто, но современные ИИ хорошо помогают.

7. Нет читерству: правило Кю

Я также размышлял о злоупотреблении игровыми механиками – диктовке ходов соперника с самого начала, удержании его в одном блоке, полном контроле игрового процесса и принуждении к проигрышу:

  • Я разработал правило, запрещающее отправлять соперника в один и тот же блок последовательно, назвав его правило Кю 窮, означающее «истощение» или «удушение». Это тематическая отсылка к правилу Ко в Го – игре, которую я тоже люблю – означающему «вечность» или «повторение» и предотвращающему рекурсивно бесполезные ходы.
  • Проблему также можно решить случайными начальными ходами (следуя метафорам Го, я назвал бы их разведчиками) или клетками, в которые нельзя ходить (своего рода препятствиями), что также делает дебют свежим и непредсказуемым – я решил ввести и то, и другое как часть параметров поля.

8. Современная разработка

В августе 2024 я начал окончательную переработку. Используя Rust для общего ядра между сервером и клиентом, я создаю определяющую версию TacticToy. Моя цель проста: создать самую полную, стратегически глубокую и технически отточенную реализацию N-мерных крестиков-ноликов.

Начав новую версию TT на Rust, я сначала переделал старый игровой движок и алгоритм Minimax. Нуждаясь в тестировании и отладке, но не имея единственного очевидно лучшего кросс-платформенного фреймворка рендеринга для Rust, я решил сосредоточиться на расширении ядра, тестируя и отлаживая в терминале. Раньше я никогда не играл в консольные игры и не делал их, но создал одну для TacticToy, и хотя вряд ли опубликую её, мне понравилась экосистема Rust с мощными и простыми крейтами для работы с терминалом. И мне очень нравится мой алгоритм визуализации произвольных полей, включая 3D и даже 4D в виде 2D-слоёв.

После введения параметров L и D, реализации алгоритма MCTS, пришло время заняться отрисовкой и UI. Нуждаясь в поддержке 2D и 3D рендеринга, я попробовал использовать Bevy, осторожно изучая парадигму Entity-Component System (ECS) – хотя она кажется отличной для игр, полных различных объектов и механик, на кажется слишком громоздкой для игры с единственной структурой состояния фиксированного размера, хотя может быть полезна для разделения UI и физического состояния для анимаций.

Помимо технических проблем, проектирование UI для настоящей 3D стратегической игры – это действительно сложно. Я решил сосредоточиться на других функциях, оставаясь в 2D, переделывая и обновляя старый веб-UI через WASM и бэкенд-сервер, всё на Rust. Я исследовал Leptos, Yew и Dioxus – и выбрал последний за его кросс-платформенную поддержку помимо веба и множество других приятных возможностей.

На данный момент, эта версия ещё в статии тестирования, но уже весьма играбельна: 2D.TacticToy.com.