Чому FastAPI для гри?
Очевидний вибір для бекенду браузерної гри — Node.js або PHP. Ми обрали Python із FastAPI, і це виявилося правильним рішенням для такої гри, як VvW.
Покрокові браузерні RPG — це не ігри реального часу. Тут немає вимог до затримки WebSocket, немає ігрових циклів, критичних до частоти кадрів. Кожна дія — це HTTP-запит. Сервер обробляє дію, оновлює базу даних і повертає результат. Саме для цього й призначені REST API.
FastAPI дав нам:
- Async I/O — критично для пулу підключень PostgreSQL при масштабуванні
- Валідація Pydantic — кожне тіло запиту перевіряється до звернення до БД
- Автоматична документація OpenAPI — API гри самодокументується за адресою
/api/docs - Вбудована інтеграція rate limiting зі SlowAPI — без окремого стека middleware
- Async ORM SQLAlchemy 2.0 — підтримка першого класу для новітнього SQLAlchemy
Огляд архітектури
Застосунок — це єдиний додаток FastAPI із 27 роутерами, кожен з яких відповідає за певний домен функцій.
Усі роутери зареєстровані в main.py з префіксом /api/.
vampires-vs-werewolves/
├── backend/
│ ├── main.py # FastAPI app, middleware, routers
│ ├── config.py # Налаштування через pydantic-settings
│ ├── database.py # AsyncEngine, AsyncSession, get_db
│ ├── auth.py # JWT, bcrypt, get_current_user
│ ├── models.py # Усі моделі SQLAlchemy ORM
│ ├── limiter.py # Rate limiter SlowAPI
│ ├── scheduler.py # APScheduler (щоденне скидання, кланські війни)
│ ├── game_logic/
│ │ ├── battle_engine.py # Формули бою, критичні удари, навички
│ │ ├── xp_engine.py # Криві XP, логіка підвищення рівня
│ │ └── achievements.py # Засів і перевірка досягнень
│ ├── routers/ # 27 роутерів
│ │ ├── auth.py # реєстрація, вхід, оновлення, експорт даних
│ │ ├── character.py # статистика, робота, підвищення рівня
│ │ ├── battle.py # полювання, pvp, історія битв
│ │ ├── inventory.py # екіпірування, продаж, використання
│ │ ├── shop.py # магазин NPC
│ │ ├── crafting.py # рецепти, кузня, алхімія
│ │ ├── dungeons.py # 10 підземель, зустрічі з босами
│ │ ├── boss.py # Світові боси
│ │ ├── clans.py # створення, вступ, оголошення війни
│ │ ├── events.py # Затемнення, підрахунок очок Кривавого місяця
│ │ ├── quests.py # 100 квестів
│ │ ├── skills.py # 100 навичок
│ │ ├── achievements.py # 80 досягнень
│ │ ├── ranking.py # Глобальні/фракційні таблиці лідерів
│ │ ├── auction.py # Торговий майданчик гравець-гравець
│ │ ├── chat.py # Чат зони/глобальний/приватні повідомлення
│ │ ├── social.py # Друзі, стрічка активності
│ │ ├── mail.py # Внутрішньоігрова пошта
│ │ ├── notifications.py # Push-сповіщення
│ │ ├── daily.py # Щоденні нагороди входу, серія
│ │ ├── prestige.py # Система престижу ендгейму
│ │ ├── missions.py # Місії на основі часу
│ │ ├── map.py # 30 локацій
│ │ ├── admin.py # Адміністративні ендпоінти
│ │ ├── support.py # Тікети підтримки
│ │ ├── waitlist.py # Список очікування email до запуску
│ │ └── tutorial.py # Навчання для нових гравців
│ ├── alembic/ # 13 міграцій бази даних
│ └── tests/ # 33 тестові файли, 300+ тестів
├── static/
│ ├── css/ # 12 CSS файлів (змінні, компоненти)
│ ├── js/ # cookie-consent.js тощо
│ └── data/ # monsters.json, items.json тощо
├── game/ # Усі HTML-сторінки гри
├── blog/ # SEO блог-пости
└── legal/ # Умови, Політика конфіденційності
Дизайн бази даних
База даних має 35+ таблиць. Основний граф моделей виглядає так:
User → Character → Equipment (один до одного)
Character → InventoryItem[]
Character → CharacterSkill[]
Character → ActiveQuest[]
Character → Clan (через ClanMember)
Найскладніша таблиця — BattleLog. Вона фіксує кожен PvE і PvP бій із повним
покрокровим журналом (зберігається як JSON). Це забезпечує:
- Повтори журналу битв
- Підрахунок очок фракцій Затемнення (агрегується одним SQL-запитом)
- Відстеження прогресу досягнень (напр., "виграти 100 PvP-битв")
- Аналіз антифроду (незвичайні показники перемог, неможливі числа шкоди)
Ми використовували Alembic для міграцій з самого початку. Урок: завжди налаштовуйте Alembic до написання першої моделі. Додавати його пізніше до існуючої схеми — болісно.
Система автентифікації
Аутентифікація базується на JWT з обертанням токенів доступу + оновлення. Токен доступу спливає за 15 хвилин;
токени оновлення діють 7 днів. Обидва зберігаються на стороні клієнта в localStorage
(не httpOnly cookies, оскільки гра працює як статичний SPA без серверного рендерингу).
Заходи безпеки для зберігання токенів у localStorage:
- Заголовок CSP блокує будь-яке вбудоване впровадження скриптів на домені
- X-Frame-Options: DENY запобігає клікджекінгу
- Rate limiting на всіх ендпоінтах автентифікації (3 спроби реєстрації/годину на IP)
- Обертання токена оновлення — крадіжка старого токена оновлення не дає результату після обертання
- Непрозорі токени оновлення (64-байтовий випадковий, зберігається як хешоване значення у БД)
Паролі хешуються за допомогою bcrypt, rounds=12. Ендпоінт експорту даних GDPR
повертає всі дані користувача, але явно виключає hashed_password.
Рушій ігрової логіки
Бойовий рушій — це серце гри. Він знаходиться в
backend/game_logic/battle_engine.py і обробляє:
- Обчислення шкоди з варіацією:
base_dmg × random(0.85, 1.15) - Формула зниження захисту:
damage_reduction = DEF / (DEF + 50) - Критичні удари: шанс
LCK / 100на шкоду 1.75× - Ухилення: шанс
DEX / (DEX + 30)уникнути будь-якої шкоди - Ефекти навичок: кровотеча, оглушення, крадіжка здоров'я, посилення шкоди
- Запобіжник: битви завершуються після максимум 50 раундів для уникнення нескінченних циклів
Формула PvP використовує рейтинг ELO для матчмейкінгу та рейтингу, з обмеженням ±400 очок для атак (ви не можете атакувати когось на 400 очок ELO вище за себе). Це запобігає фармінгу новачків гравцями високого рівня.
Фронтенд: без фреймворку
Ми свідомо уникали React, Vue та Angular. Причини:
- Посторінкова модель RPG ідеально підходить для багатосторінкових застосунків. Кожна ігрова дія переходить на нову сторінку (результати полювання, оформлення в магазині, вхід до підземелля). Саме так працював BiteFight у 2006 році. Це працює й досі.
-
Нуль кроків збірки. Фронтенд — це звичайний HTML + CSS + vanilla JS.
Без webpack, без Babel, без
node_modules. Весь фронтенд розгортається як статичні файли. - SEO — просто. Кожна сторінка — це реальний HTML-файл, який сканери можуть індексувати безпосередньо. Без SSR, без складнощів гідратації.
Об'єкт-помічник api обертає всі виклики fetch() з автоматичним
впровадженням заголовка JWT та логікою оновлення токена. Кожна сторінка включає цей помічник і викликає
відповідні ендпоінти API при завантаженні.
Рівні безпеки
Ми цілилися на відповідність OWASP Top 10 із першого дня:
| Категорія OWASP | Захист |
|---|---|
| A01 — Зламаний контроль доступу | JWT-аутентифікація на кожному захищеному ендпоінті, залежність require_admin для адмін-маршрутів, журнал аудиту для 401/403/429 |
| A02 — Криптографічні збої | bcrypt 12 раундів, JWT HS256, HTTPS через Nginx HSTS |
| A03 — Ін'єкція | SQLAlchemy ORM (лише параметризовані запити), валідація вхідних даних Pydantic |
| A05 — Неправильна конфігурація безпеки | CSP, X-Frame-Options, заголовки X-XSS-Protection через SecurityHeadersMiddleware |
| A07 — Збої автентифікації | Rate limiting на автентифікації (3 спроби/годину), обертання токена оновлення, скасування токена при виході |
| A09 — Збої логування | Модель AuditLog — усі події 401/403/429 записуються до БД + ендпоінт honeypot |
Засвоєні уроки
-
Міграції Alembic із першого дня. Ми починали з
create_all()у розробці для швидкості, але налаштування правильних міграцій Alembic заощаджує величезний біль під час синхронізації staging/production. - Seed-дані — це функція, а не запізніла думка. Сівач досягнень запускається при старті й є ідемпотентним. Такий же підхід для даних монстрів, каталогів предметів і визначень квестів. Завантаження статичних ігрових даних із JSON-файлів і посів у БД при старті набагато чистіше, ніж запити до JSON-файлів під час виконання.
- Rate limiting для всього із першого дня. SlowAPI тривіально інтегрувати, і перший крок хакера — завжди атакувати ендпоінти автентифікації. Ми додали rate limits до написання першого роутера.
- Планувальник є необхідністю. APScheduler, що виконує щоденні скидання, очищення токенів та вирішення кланських воєн у фоновому режимі, робить гру живою. Не запускайтеся без запланованих завдань — ігри живуть і вмирають завдяки своєму ритму.
-
Тестуйте на SQLite, розгортайте на PostgreSQL. Ми використовуємо SQLite+aiosqlite для тестів CI
(швидко, без docker-compose). Безперебійно працює з абстракцією діалектів SQLAlchemy.
Єдина проблема: SQLite за замовчуванням не застосовує зовнішні ключі — запускайте
PRAGMA foreign_keys = ONу налаштуванні тестів.
Що далі
DevLog #2 охопить систему PvP — зокрема, як ми розробили рейтинг на основі ELO, механіку вікна імунітету, що захищає нових гравців, та планувальник автоматичного розв'язання кланських воєн.
DevLog #3 буде присвячений балансу — математиці за кривою XP, золотій економіці та тому, як ми налаштовуємо складність зустрічей, щоб кожен діапазон рівнів був відповідно складним без відчуття стіни прокачки.
Повний вихідний код буде доступний після запуску публічної бети. Приєднуйтеся до списку очікування, щоб отримати сповіщення.