Моя история разработки инкрементальной игры о горнодобывающей промышленности Кузбасса с подробным разбором технической архитектуры, системы безопасности и монетизации.
Игра на 80% сделана с помощью вайб кодинга, но это не так просто как звучит.

? Идея проекта

Я родом из Кемеровской области (Кузбасс) - угольной столицы России. Регион известен своими месторождениями: угля, золота, редких металлов. Идея пришла простая: создать современную idle-игру про управление горнодобывающей империей, где все месторождения - реальные объекты региона!

? Ключевые показатели проекта:

  • Старт разработки: 22 августа 2025

  • Версия: 1.8.0

  • Технологии: Impact.js, PHP 7.4+, MySQL 8.0, Telegram WebApp API

  • Платформы: Telegram Mini Apps, VK Mini Apps, Web

  • Архитектура: Client-Server с серверной авторитативностью

Концепция игры:

  • Начинаешь с Антоновского рудника (пгт. Рудничный) (моя родина )

  • Развиваешься до легендарной шахты Распадская

  • 15 реальных месторождений с историческими данными

  • Образовательный элемент - игроки узнают об экономике региона

?️ Технический стек

Impact.js, PHP 8+, MySQL 8.0, Telegram WebApp API

Почему Impact.js?

Выбрал Impact.js по нескольким причинам:

  1. Canvas-рендеринг - плавная анимация даже на слабых устройствах

  2. Entity система - удобно для управления игровыми объектами

  3. Встроенная физика - для анимаций рабочих и лифта

  4. Малый вес - быстрая загрузка в Telegram WebApp

⚠️ Проблема: Impact.js устарел и документация скудная. Пришлось изучать практически "методом тыка"

?️ Архитектура проекта

Структура доменов

Проект разделён на два домена:

Домен

Назначение

Технологии

game.kuzbass-empire.ru

Игра (Telegram/VK Mini Apps)

Impact.js + Canvas, PHP API

kuzbass-empire.ru

Лендинг + Личный кабинет

PHP + HTML/CSS, REST API

Есть тестовый домен, но его не вижу смысла указывать, там вечная каша ?

Серверная архитектура

API Gateway: Все игровые операции идут через единую точку входа /api/v1.php. Это скрывает структуру бэкенда и упрощает защиту.

// Клиент отправляет:
POST /api/v1.php
{
  "action": "buy_upgrade",
  "user_id": kuzbassANJ8112,
  "session_token": "kuztokenJjksa119",
  "price": 777
}

// Gateway маршрутизирует на handler:
api/***/buy_upgrade.***.php

12 handlers (модульная система):

  • load.handler.php - загрузка прогресса игрока

  • save.handler.php - сохранение прогресса

  • heartbeat.handler.php - отслеживание времени в игре

  • check_session.handler.php - валидация сессии

  • buy_upgrade.handler.php - покупки и улучшения

  • spend_kuzbass.handler.php - траты премиум-валюты

  • exchange_currency.handler.php - обмен Кузбиков на монеты

  • ...и другие

✅ Преимущества подхода: Легко добавлять новые действия, централизованная обработка ошибок, невозможно узнать структуру API через DevTools.

?️ База данных (17 таблиц)

Полностью server-authoritative подход - все критичные данные хранятся и валидируются на сервере:

Таблица

Назначение

userz_kuzb

Профили игроков (Telegram ID, имя, премиум-статус)

game_savez_kuzb

Игровой прогресс (JSON + критичные поля)

auth_tokenz_kuzb

OAuth токены для входа через бота

referralz_kuzb

Реферальная система с milestone tracking

cheat_attemptz_kuzb

Логирование попыток читерства

kuzbass_transactionz_kuzb

История покупок премиум-валюты

activity_logz_kuzb

Аудит всех действий пользователей и админов

leaderboard_kuzb

Кэш рейтинга игроков

game_settingz_kuzb

Динамические настройки игровой экономики

depositz_kuzb

Справочник месторождений (15 объектов)

Гибридное хранение прогресса

Разработал систему "JSON + критичные поля":

CREATE TABLE `game_savez_kuzb` (
  `user_id` INT PRIMARY KEY,
  `save_data` TEXT,           -- Полный JSON прогресса
  `cash` DOUBLE,              -- Дублируется для быстрых запросов
  `kuzbass` DOUBLE,           -- Премиум-валюта
  `income_per_hour` DOUBLE,   -- Для рейтинга
  `investors_count` INT,      -- Для рейтинга
  `total_earned` DOUBLE,      -- Статистика
  `admin_updated` TINYINT     -- Флаг админ-правки
);

Зачем дублирование?

  • ✅ Быстрые SQL-запросы для рейтинга (без парсинга JSON)

  • ✅ Индексы на критичных полях

  • ✅ Защита от манипуляций (сервер сверяет JSON с полями)

? Система безопасности (многоуровневая)

1. Server-Authoritative подход

Клиент НИКОГДА не решает сам - только сервер!

// ❌ ПЛОХО (клиент решает):
player.money -= 1000;
saveToServer(player);

// ✅ ХОРОШО (сервер решает):
const response = await buyUpgrade(price);
if (response.success) {
    player.money = response.new_cash; // От сервера!
}

2. Session Token система

Каждый игрок получает уникальный токен при входе:

// При авторизации:
$sessionToken = bin2hex(random_bytes(32)); // 64 символа
$db->update('users', ['session_token' => $sessionToken]);

// При КАЖДОМ запросе:
if ($user['session_token'] !== $requestToken) {
    die(json_encode(['error' => 'Invalid session']));
}

Защита от IDOR (Insecure Direct Object Reference):

  • Нельзя загрузить чужой прогресс даже зная user_id

  • Все эндпоинты валидируют session_token

  • При входе с нового устройства старая сессия сбрасывается

3. Античит система (двухуровневая)

Клиентская детекция:

  • Определение открытой консоли DevTools

  • Обнаружение изменений localStorage/sessionStorage

  • Детект попыток вызова скрытых функций

Серверная валидация:

  • Проверка адекватности изменений (нельзя заработать много монет за 1 секунду)

  • Валидация последовательности операций

  • Автобан после 5 попыток читерства

  • Уведомления админам в Telegram

  • Уведомление-предупрежние пользователю в Telegram что так делать нехорошо

4. Скрытие структуры API

Использую несколько методов obfuscation:

// .htaccess - возврат 404 вместо 403
RewriteRule ^.*\.php$ - [R=404,L]

// Все запросы через Gateway
POST /api/v1.php (action в body)

// Проверка Referer
RewriteCond %{HTTP_REFERER} !^https://([a-z0-9-]+\.)?kuzbass-empire\.ru

? Игровые механики

Idle-геймплей с глубиной

Основной цикл:

  1. Добыча ресурсов на месторождениях

  2. Транспортировка подъёмником

  3. Продажа со склада

  4. Реинвестирование в улучшения

Узкие места (bottleneck механика):

// Реальный доход ограничен вместимостью!
const shaftProduction = ∑(shaft.income); // Добыча шахт
const elevatorCap = elevator.capacity;    // Вместимость лифта
const warehouseCap = warehouse.capacity;  // Вместимость склада

const realIncome = min(shaftProduction, elevatorCap, warehouseCap);
// Если склад слабый - доход упадёт!

? Логичная игра: Игроки должны балансировать развитие всех элементов, а не только месторождений. Это добавляет стратегическую глубину!

Престиж-система (IPO)

Классическая механика idle-игр с математическим балансом:

// Формула расчёта инвесторов:
const divisor = 44444444444.444;
const power = 0.5; // Корень квадратный

investorsGain = Math.floor(
    Math.pow(lifetimeEarning / divisor, power) -
    Math.pow(startingLifetime / divisor, power)
);

// Бонус к доходу:
incomeBonus = investors × 0.02; // 2% за инвестора

Все параметры (divisorpower, процент бонуса) - динамические, хранятся в БД и настраиваются через админку.

? OAuth авторизация (свой велосипед)

Telegram Login Widget не подходил, потому что в настройках бота можно указать только один поддомен, то есть нельзя одного бота подключить для авторизации в кабинете на kuzbass-empire.ru и одновременно на поддомене game,kuzbass-empire.ru. Пришлось придумывать выход из ситуации и по итогу разработал свою OAuth-систему через бота:

Схема работы:

Пользователь на сайте или в игре → кликает "Войти через бота"
Генерируется токен (POST /api/auth_generate_token.php)
   └─> Сохраняется в auth_tokens (TTL 5 минут)
Открывается бот с deeplink: t.me/bot?start=TOKEN
Бот отправляет токен на сервер + telegram_id пользователя
Сервер связывает токен с telegram_id
Сайт и игра polling (каждые 2 сек): проверяет статус токена
Токен подтверждён → получаем данные пользователя → вход!

Безопасность:

  • Токен используется только 1 раз

  • Время жизни 5 минут

  • Привязка к IP и User-Agent и ещё нескольким параметрам

  • Функция "Запомнить устройство" через localStorage

? Монетизация (двойная валюта)

Кузбики ?️ (премиум-валюта)

Источники получения:

  • Стартовый бонус: 100 Кузбиков

  • Рефералы: 250 Кузбиков за друга (milestone 10K монет)

  • Покупка: ЮMoney, FreeKassa (курс настраиваемый)

Применение:

  • Сброс перезарядки навыка менеджера (10 Кузбиков)

  • Обмен на игровые монеты (курс 1:1000, комиссия 0-10%)

Pending система начислений

Разработал систему отложенного начисления для безопасности:

// 1. Callback от платёжки:
$user['pending_kuzbass_change'] += 5500;

// 2. Игра проверяет каждые 10 сек:
if ($pending > 0) {
    $user['kuzbass'] += $pending;
    $user['pending_kuzbass_change'] = 0;
    // Показываем уведомление в игре
}

Зачем? Защита от race conditions - нельзя потратить Кузбики пока транзакция не подтверждена полностью, а то уже есть такие умники, которые нажмают ускорение перезарядки за кузбики и резко перезагружают страницу, в таком случае списания кузбиков не было и можно было бесплатно перезаряжать сколько угодно ?

? Кроссплатформенность (Telegram + VK)

Telegram Mini Apps

Полная интеграция с Telegram WebApp API:

  • Авторизация - через initDataUnsafe

  • Тема - автоматическая адаптация под тему устройства

  • Haptic Feedback - вибрации при действиях

  • Header/BottomBar - цвета под тему

VK Mini Apps

Адаптация заняла пару часов благодаря модульной архитектуре:

// Определение платформы:
const isVK = window.location.search.includes('vk_');

// VK Bridge инициализация:
if (isVK && window.vkBridge) {
    vkBridge.send('VKWebAppInit');
    // Авторизация через VK
}

Единый бэкенд для обеих платформ - только фронтенд различается!

?️ Защита от читерства (многослойная)

Проблема

В браузерной игре игрок имеет доступ к:

  • JavaScript коду (можно вызывать функции)

  • Памяти (можно менять переменные)

  • Network (можно повторять запросы)

Решение #1: Серверная валидация

// Проверка адекватности изменений:
$timeDiff = time() - $lastSaveTime;
$maxPossibleEarning = $income_per_hour / 3600 * $timeDiff * 1.5;

if ($cashDiff > $maxPossibleEarning) {
    logCheatAttempt($userId, 'impossible_earning');
    return error('Suspicious activity');
}

Решение #2: Race Condition защита

// Флаги операций в клиенте:
window._upgradeOperationInProgress = true;

// Дублирование через sendBeacon:
navigator.sendBeacon('/api/v1.php', data); // Надёжная доставка
fetch('/api/v1.php', data);                // Получение ответа

Решение #3: Session binding

При каждом сохранении сервер возвращает актуальные данные:

// Ответ сервера:
{
    "success": true,
    "new_cash": 12345.67,    // Актуальный баланс
    "new_kuzbass": 150,      // Актуальные Кузбики
    "server_time": 1699120345
}

// Клиент применяет:
player.money = response.new_cash; // Не своё значение!

⚡ Оптимизация производительности

Автосохранение (умное)

Сохранение каждые 10 секунд, но с защитой:

// Heartbeat отдельно от save:
setInterval(() => {
    sendHeartbeat(timePlayed); // Только время
}, 30000); // 30 сек

setInterval(() => {
    saveGame(fullData); // Полные данные
}, 10000); // 10 сек

Это снижает нагрузку на БД на 66%!

Бандлинг ресурсов

Вместо 15 отдельных JS файлов - один бандл:

// scripts-bundle.php объединяет:
$files = [
    '***/scripts/reset.js',
    'и другие'
];

// Отдаём один файл с мгновенным применением изменений при новой загрузке страницы:
/api/scripts-bundle.php?v={timestamp}

Результат: Загрузка игры ускорилась с 3.2s до 0.8s!

Capture Phase для UI

Проблема: При клике на UI игра ставилась на паузу (Impact.js перехватывал события).

Решение:

// ❌ Bubbling phase (было):
element.addEventListener('click', handler, false);

// ✅ Capture phase (стало):
element.addEventListener('click', handler, true);
// Теперь UI перехватывает события ДО игры!

? Модернизация UI (от Canvas к HTML)

Impact.js использует Canvas для попапов. Это выглядит устаревшим и не адаптивно.

Решение: Перехват создания попапов и замена на HTML-шторки!

// Патч spawnEntity:
const originalSpawn = ig.game.spawnEntity;
ig.game.spawnEntity = function(entityClass, x, y, settings) {
    
    if (entityClass.name === 'EntityUpgradeController') {
        openUpgradeSheet(); // Наша HTML-шторка!
        return { kill: () => {}, _wasReplaced: true };
    }
    
    return originalSpawn.call(this, entityClass, x, y, settings);
};

Результат:

  • ✅ Современный дизайн в стиле Web3 приложения

  • ✅ Анимации (slide-up/slide-down)

  • ✅ Адаптация под тему устройства пользователя (светлая/тёмная)

  • ✅ Копирование данных (реферальная ссылка)

  • ✅ Сохранение всего функционала игры

? Платёжные системы (интеграция)

ЮMoney + FreeKassa

Интегрировал обе платёжки с единым интерфейсом:

Возможность

ЮMoney

FreeKassa

Банковские карты

✅ МИР, Visa, MC

❌ (конские условия)

Электронные кошельки

✅ ЮMoney

❌ (конские условия)

Криптовалюта

✅ BTC, ETH, USDT, TON

Подпись callback

SHA-256

MD5

Почему именно ЮMoney и FreeKass? Потому что я решил на первых этапах игры принимать платежи как физ.лицо, а это единственный сервисы, которые я знаю с адекватной интеграцией и, главное, возможностью принимать как физ.лицо.
Изначально только фрикасу планировал подключить, но когда магазин был одобрен и я настроил интеграцию, то столкнулся с тем, что минимальный платёж - 1000 рублей и его нельзя настроить ?

Динамическая настройка через админку:

  • Включение/выключение систем

  • Логотипы и названия

  • Бонусы к пополнению (%, настраиваемо)

  • Минимальные/максимальные суммы

? Аналитика и мониторинг

Логирую ВСЁ через таблицу activity_logz_kuzb:

// Примеры событий:
- register (регистрация)
- login (вход)
- buy_upgrade (покупка)
- kuzbass_topup (пополнение)
- referral_kuzbass_bonus (бонус реферала)
- cheat_attempt (попытка читерства)
- admin_update_user (админ изменил данные)

Админ-панель показывает:

  • Онлайн игроков в реальном времени

  • Топ читеров с деталями попыток

  • История транзакций

  • Графики активности по дням

  • Детальная информация о каждом игроке

? Масштабируемость

Динамическая конфигурация

ВСЕ параметры игры настраиваемые:

// Таблица game_settingz_kuzb (24 параметра):
- mine_price_coef: 1.40
- other_price_coef: 1.50
- manager_skill_duration: 120
- max_offline_hours: 24
- referral_kuzbass_bonus: 100
- kuzbass_exchange_rate: 1000
...и другие

Зачем? Можно балансировать экономику БЕЗ обновления кода! Изменения применяются мгновенно для всех игроков.

Поддержка 15 месторождений (расширяемая)

// Патч для поддержки динамического числа шахт:
const actualShaftsCount = window.DEPOSITS_DATA?.length || 15;

// Добавление пустых слотов:
while (sessionData.Mineshaft.length < actualShaftsCount) {
    sessionData.Mineshaft.push({
        level: 0,
        managerExist: false,
        ...defaultShaftData
    });
}

? Проблемы и решения

Проблема #1: Откат баланса после покупок

Причина: Автосохранение срабатывало ДО завершения покупки на сервере.

Решение:

// Синхронизируем баланс ПЕРЕД покупкой:
await saveGameToServer();
await delay(500); // Ждём завершения
const response = await buyUpgrade(price);

// Применяем СЕРВЕРНЫЙ баланс:
player.money = response.new_cash;

Проблема #2: iOS не открывает платёжные формы

Причина: Safari блокирует window.open вне обработчика клика.

Решение:

// Создаём скрытую форму + auto-submit:
const form = document.createElement('form');
form.method = 'POST';
form.action = paymentURL;
form.target = '_blank';
document.body.appendChild(form);
form.submit(); // Работает в Safari!

Проблема #3: Игра ставится на паузу при открытии UI

Решение: Переопределение pauseGame + агрессивный мониторинг каждые 50ms.

? Выводы

За несколько месяцев создал полноценную idle-игру с:

  • ✅ Глубокими игровыми механиками

  • ✅ Многоуровневой системой безопасности

  • ✅ Двумя платформами (Telegram + VK)

  • ✅ Монетизацией через 2 платёжные системы

  • ✅ Реферальной программой

  • ✅ Админ-панелью для управления

  • ✅ Образовательным компонентом (реальные месторождения)

Главный урок: Даже на устаревшем движке (Impact.js) можно создать современную, безопасную и красивую игру, если продумать архитектуру и не жалеть времени на детали.

? Игра доступна:

Telegram: @kuzbass_empire_bot
VK: vk.com/app54256088
Сайт: kuzbass-empire.ru

Комментарии (4)


  1. alex_02
    08.11.2025 09:46

    Неплохо, неплохо, но над системой вычисления людей, которые в браузере решили залезть в консоль разработчика, стоит ещё поработать. Мне прилетело предупреждение, что так нельзя делать, когда игра только только открылась в браузере. Мой браузер — Chrome ))