Dlaczego FastAPI do gry?
Oczywistymi wyborami dla backendu przeglądarkowej gry są Node.js lub PHP. My wybraliśmy Pythona z FastAPI i okazało się to właściwą decyzją dla gry takiej jak VvW.
Turowe przeglądarkowe RPG to nie gry czasu rzeczywistego. Nie ma wymagań dotyczących opóźnień WebSocket ani pętli gry krytycznych dla liczby klatek. Każda akcja to żądanie HTTP. Serwer rozstrzyga akcję, aktualizuje bazę danych i zwraca wynik. Dokładnie do tego służą REST API.
FastAPI zapewnił nam:
- Async I/O — kluczowe dla puli połączeń PostgreSQL przy skali
- Walidację Pydantic — każde ciało żądania jest walidowane przed dotkięciem bazy danych
- Automatyczną dokumentację OpenAPI — API gry samo się dokumentuje pod
/api/docs - Wbudowaną integrację ograniczania szybkości ze SlowAPI — bez oddzielnego stosu middleware
- Asynchroniczny ORM SQLAlchemy 2.0 — wsparcie pierwszej klasy dla najnowszego SQLAlchemy
Przegląd architektury
Aplikacja to pojedyncza aplikacja FastAPI z 27 routerami, z których każdy obsługuje domenę funkcji.
Wszystkie routery są rejestrowane w main.py pod prefiksem /api/.
vampires-vs-werewolves/
├── backend/
│ ├── main.py # Aplikacja FastAPI, middleware, routery
│ ├── config.py # Ustawienia przez pydantic-settings
│ ├── database.py # AsyncEngine, AsyncSession, get_db
│ ├── auth.py # JWT, bcrypt, get_current_user
│ ├── models.py # Wszystkie modele ORM SQLAlchemy
│ ├── limiter.py # Ogranicznik szybkości SlowAPI
│ ├── scheduler.py # APScheduler (dzienny reset, wojny klanów)
│ ├── game_logic/
│ │ ├── battle_engine.py # Formuły walki, krytyki, umiejętności
│ │ ├── xp_engine.py # Krzywe XP, logika awansowania
│ │ └── achievements.py # Seedowanie i sprawdzanie osiągnięć
│ ├── routers/ # 27 routerów
│ │ ├── auth.py # rejestracja, logowanie, odświeżanie, eksport danych
│ │ ├── character.py # statystyki, praca, awansowanie
│ │ ├── battle.py # polowanie, pvp, historia bitew
│ │ ├── inventory.py # wyposażanie, sprzedaż, użycie
│ │ ├── shop.py # sklep NPC
│ │ ├── crafting.py # receptury, kucie, alchemia
│ │ ├── dungeons.py # 10 lochów, spotkania z bossami
│ │ ├── boss.py # Bossowie świata
│ │ ├── clans.py # tworzenie, dołączanie, wypowiedzenie wojny
│ │ ├── events.py # Wojna Zaćmienia, punktacja Krwawego Księżyca
│ │ ├── quests.py # 100 zadań
│ │ ├── skills.py # 100 umiejętności
│ │ ├── achievements.py # 80 osiągnięć
│ │ ├── ranking.py # Globalne/frakcyjne rankingi
│ │ ├── auction.py # Rynek gracz-gracz
│ │ ├── chat.py # Czat strefowy/globalny/DM
│ │ ├── social.py # Znajomi, feed aktywności
│ │ ├── mail.py # Poczta w grze
│ │ ├── notifications.py # Powiadomienia push
│ │ ├── daily.py # Dzienne nagrody za logowanie, seria
│ │ ├── prestige.py # System prestiżu endgame
│ │ ├── missions.py # Misje czasowe
│ │ ├── map.py # 30 lokacji
│ │ ├── admin.py # Endpointy administracyjne
│ │ ├── support.py # Zgłoszenia supportu
│ │ ├── waitlist.py # Lista oczekujących przed premierą
│ │ └── tutorial.py # Samouczek dla nowych graczy
│ ├── alembic/ # 13 migracji bazy danych
│ └── tests/ # 33 pliki testów, ponad 300 testów
├── static/
│ ├── css/ # 12 plików CSS (zmienne, komponenty)
│ ├── js/ # cookie-consent.js itp.
│ └── data/ # monsters.json, items.json itp.
├── game/ # Wszystkie strony HTML gry
├── blog/ # Posty bloga SEO
└── legal/ # Regulamin, Polityka prywatności
Projekt bazy danych
Baza danych ma ponad 35 tabel. Podstawowy graf modeli wygląda następująco:
User → Character → Equipment (jeden do jednego)
Character → InventoryItem[]
Character → CharacterSkill[]
Character → ActiveQuest[]
Character → Clan (przez ClanMember)
Najtrudniejszą tabelą jest BattleLog. Rejestruje każdą walkę PvE i PvP z pełnym
rozpisaniem runda po rundzie (przechowywane jako JSON). Napędza to:
- Replay historii bitew
- Punktację frakcyjną Wojny Zaćmienia (zagregowana jednym zapytaniem SQL)
- Śledzenie postępu osiągnięć (np. „wygraj 100 walk PvP")
- Analizę antycheatową (niezwykłe wskaźniki zwycięstw, niemożliwe liczby obrażeń)
Używaliśmy Alembic do migracji od samego początku. Wniosek: zawsze konfiguruj Alembic zanim napiszesz swój pierwszy model. Dodawanie go później do istniejącego schematu jest bolesne.
System uwierzytelniania
Auth jest oparty na JWT z rotacją tokenów dostępu + odświeżania. Token dostępu wygasa po 15 minutach;
tokeny odświeżania ważne przez 7 dni. Oba są przechowywane po stronie klienta w localStorage
(nie w ciasteczkach httpOnly, ponieważ gra działa jako statyczny SPA bez renderowania po stronie serwera).
Środki bezpieczeństwa dla przechowywania tokenów w localStorage:
- Nagłówek CSP blokuje wszelkie wstrzykiwanie skryptów inline w domenie
- X-Frame-Options: DENY zapobiega clickjackingowi
- Ograniczanie szybkości na wszystkich endpointach auth (3 próby rejestracji/godzinę na IP)
- Rotacja tokenów odświeżania — kradzież starego tokenu odświeżania nie działa po rotacji
- Nieprzezroczyste tokeny odświeżania (64-bajtowe losowe, przechowywane jako zahashowana wartość w DB)
Hasła są hashowane przez bcrypt, rounds=12. Endpoint eksportu danych GDPR
zwraca wszystkie dane użytkownika, ale wyraźnie wyklucza hashed_password.
Silnik logiki gry
Silnik walki to serce gry. Mieszka w
backend/game_logic/battle_engine.py i obsługuje:
- Obliczanie obrażeń z wariancją:
base_dmg × random(0.85, 1.15) - Formuła redukcji obrony:
damage_reduction = DEF / (DEF + 50) - Trafienia krytyczne: szansa
LCK / 100na 1,75× obrażeń - Unik: szansa
DEX / (DEX + 30)na uniknięcie wszystkich obrażeń - Efekty umiejętności: krwawienie, ogłuszenie, kradzież życia, wzmocnienie obrażeń
- Zabezpieczenie: bitwy kończą się po max 50 rundach, aby zapobiec nieskończonym pętlom
Formuła PvP używa rankingu ELO do matchmakingu i rankingowania, z ograniczeniem ±400 punktów podczas ataków (nie możesz zaatakować kogoś 400 punktów ELO powyżej ciebie). Zapobiega to farmowaniu początkujących przez graczy wysokopoziomowych.
Frontend: bez frameworka
Celowo unikaliśmy Reacta, Vue i Angulara. Powody:
- Model RPG oparty na stronach doskonale pasuje do aplikacji wielostronicowych. Każda akcja gry nawiguje do nowej strony (wyniki polowania, kasa sklepu, wejście do lochu). Tak działał BiteFight w 2006 roku. Działa do dziś.
-
Zero kroków budowania. Frontend to czyste HTML + CSS + vanilla JS.
Brak webpack, brak Babel, brak
node_modules. Cały frontend wdraża się jako pliki statyczne. - SEO jest proste. Każda strona to prawdziwy plik HTML, który boty mogą indeksować bezpośrednio. Nie potrzeba SSR, brak złożoności hydracji.
Obiekt pomocniczy api opakowuje wszystkie wywołania fetch() z automatycznym
wstrzykiwaniem nagłówka JWT i logiką odświeżania tokenów. Każda strona zawiera ten helper i wywołuje
odpowiednie endpointy API przy ładowaniu.
Warstwy bezpieczeństwa
Od pierwszego dnia celowaliśmy w zgodność z OWASP Top 10:
| Kategoria OWASP | Środek zaradczy |
|---|---|
| A01 — Zepsuty Kontrola Dostępu | Auth JWT na każdym chronionym endpoincie, zależność require_admin dla tras admina, dziennik audytu dla 401/403/429 |
| A02 — Błędy Kryptograficzne | bcrypt 12 rund, JWT HS256, HTTPS egzekwowane przez Nginx HSTS |
| A03 — Wstrzykiwanie | ORM SQLAlchemy (tylko sparametryzowane zapytania), walidacja danych wejściowych Pydantic |
| A05 — Błędna Konfiguracja Bezpieczeństwa | Nagłówki CSP, X-Frame-Options, X-XSS-Protection przez SecurityHeadersMiddleware |
| A07 — Błędy Uwierzytelniania | Ograniczanie szybkości auth (3 próby/godzinę), rotacja tokenów odświeżania, unieważnianie tokenów przy wylogowaniu |
| A09 — Błędy Logowania | Model AuditLog — wszystkie zdarzenia 401/403/429 zapisywane do DB + endpoint honeypot |
Wnioski
-
Migracje Alembic od pierwszego dnia. Zaczęliśmy od
create_all()w fazie development dla szybkości, ale wczesna konfiguracja właściwych migracji Alembic oszczędza ogromnego bólu podczas synchronizacji staging/production. - Dane startowe to funkcja, nie afterthought. Seeder osiągnięć uruchamia się przy starcie i jest idempotentny. To samo podejście dla danych potworów, katalogów przedmiotów i definicji zadań. Ładowanie statycznych danych gry z plików JSON i seedowanie do DB przy starcie jest znacznie czystsze niż odpytywanie plików JSON w czasie rzeczywistym.
- Ograniczanie szybkości wszystkiego od pierwszego dnia. SlowAPI jest trywialny do integracji, a pierwszym ruchem hakera jest zawsze bombardowanie endpointów auth. Dodaliśmy limity szybkości przed napisaniem pierwszego routera.
- Scheduler jest niezbędny. APScheduler wykonujący dzienne resety, czyszczenie tokenów i rozstrzyganie wojen klanów w tle sprawia, że gra czuje się żywa. Nie uruchamiaj bez zaplanowanych zadań — gry żyją i umierają przez swój rytm.
-
Testuj w SQLite, wdrażaj na PostgreSQL. Używamy SQLite+aiosqlite do testów CI
(szybkie, bez docker-compose). Działa bezproblemowo z abstrakcją dialektów SQLAlchemy.
Jedyna pułapka: SQLite domyślnie nie wymusza kluczy obcych — uruchom
PRAGMA foreign_keys = ONw konfiguracji testów.
Co dalej?
DevLog #2 obejmie system PvP — konkretnie jak zaprojektowaliśmy ranking oparty na ELO, mechanikę okna odporności chroniącą nowych graczy i scheduler automatycznego rozstrzygania wojen klanów.
DevLog #3 będzie o balansie — matematyce za krzywą XP, gospodarką złota i jak dostrajamy trudność spotkań, aby każdy zakres poziomów był odpowiednio wymagający bez uczucia grindowania.
Pełny kod źródłowy zostanie udostępniony po publicznym uruchomieniu beta. Dołącz do listy oczekujących, aby zostać powiadomionym.