700 Bin Oyuncu Aynı Anda Indie Multiplayer Oyununuza Girince Ne Olur (Ve Nasıl Hayatta Kalırsınız)
Özet olarak
700 bin eşzamanlı oyuncunun aynı anda oyununuza girmesiyle backend'in nasıl çöktüğünü ve hayatta kalmak için P2P, lobi yönetimi ve matchmaking stratejilerini keşfedin.
Her indie geliştirici bir gecede viral olma hayali kurar. Oyununuz Twitch'te patlar, Steam eşzamanlı oyuncu sayısı bir haftada 200'den 200.000'e fırlar ve birden sektörün konuşulanı olursunuz. Bu hayalde kimsenin size söylemediği şey, saat sabah 3'te matchmaking servisiniz yanarken, lobi veritabanınız yazma çekişmesi hataları fırlatırken ve Discord'unuz tek bir oyuna bağlanamayan oyunculardan geçilmezken backend'inizin ne halde olduğudur.
Bu varsayımsal değil. Goose Goose Duck 2022'nin sonlarında viral olduğunda, kendini daha önce mega-hit deneyimi olmayan küçük bir ekip olarak tanımlayan Gaggle Studios, eşzamanlı oyuncu sayısının 700.000'i aştığını gördü. Backend'leri dayandı. Sonsuz kaynakları olduğu için değil, daha önce bu dalgayı atlatmalarını sağlayacak belirli mimari kararlar aldıkları için.
Bu yazı, tam olarak bu kararların neler olduğunu, multiplayer bir oyun viral olduğunda önce neyin kırıldığını ve trafik gelmeden önce kendi projenize uygulayabileceğiniz somut desenleri adım adım açıklıyor.
Viral Bir Multiplayer Dalgasının Anatomisi
Aslında Ne Kırılır (Sırasıyla)
Bir multiplayer oyun beklenen yükünün 10-100 katına ulaştığında, hatalar tahmin edilebilir bir sırayla kademeli olarak gelir. Bu sıralamayı anlamak kritiktir çünkü sisteminizi doğru sırada sağlamlaştırmanız gerekir.
1. Kimlik doğrulama ve giriş (ilk 48 saatte normal yükün 5-15 katı)
Oynamak isteyen her oyuncunun önce kimlik doğrulaması yapması gerekir. Steam ile doğrulanan oyunlarda Steam'in backend'i ağır yükü kaldırır, ancak sunucunuzun yine de ticket'ları doğrulaması, oyuncu profillerini oluşturması veya getirmesi ve oturum token'larını döndürmesi gerekir. Her auth isteği birincil veritabanınıza dokunuyorsa, sorununuz var demektir. Dakikada 50.000 giriş talebi ve her biri oturum oluşturma için bir PostgreSQL yazma işlemi yapıyorsa, bağlantı havuzunuz 90 saniyeden kısa sürede doyuma ulaşır.
2. Lobi keşfi ve matchmaking (normal yükün 10-50 katı)
Bu, oyuncu deneyimini gerçekten öldüren ilk domino taşıdır. 200.000 oyuncu aynı anda lobilere göz attığında, lobi listesi sorgu modeliniz "saniyede yüzlerce okuma"dan "saniyede on binlerce okuma"ya geçer. Lobi durumu birincil ilişkisel veritabanınızda yaşıyorsa, artık replikasyon gecikmesine yetişemeyen, odaları dolu olduğu halde müsait gösteren güncel olmayan lobi verileri döndüren okuma replikalarıyla savaşıyorsunuz demektir.
3. Lobi oluşturma ve katılma işlemleri (yazma ağırlıklı dalga)
Her yeni oyun lobisi bir yazma işlemidir. Bir lobiye katılan her oyuncu bir yazma işlemidir (oyuncu listesini günceller). Ayrılan her oyuncu bir yazma işlemidir. Goose Goose Duck trafiğinin zirvesinde bu, saniyede binlerce lobi durumu mutasyonu anlamına geliyordu. Gaggle Studios, gerçek oynanış için eşler arası (P2P) bir model kullandı, ancak lobi koordinasyonu yine de merkezileşmeye ihtiyaç duyuyordu—oyuncuların doğrudan bağlanabilmeleri için önce birbirlerini bulmaları gerekiyor.
4. NAT traversal ve P2P bağlantı kurulumu
Eşler arası mimarinin tavanına işte burada ulaşılıyor. STUN/TURN altyapısıyla bile P2P bağlantıları başarısız olur. Röle yedeği olmadan P2P bağlantı başarı oranı endüstri ortalaması olarak oyuncu çiftlerinin kabaca %75-85'idir. Kalan %15-25'in TURN röle sunucularına ihtiyacı vardır. 700.000 eşzamanlı oyuncu saniyede binlerce bağlantı kurmaya çalışırken, çoğu indie ekibin hiç inşa etmediği bir röle altyapısına ihtiyacınız var.
Neden P2P Doğru Seçimdi (Olana Kadar)
Gaggle Studios, Goose Goose Duck'ın gerçek oynanışı için eşler arası (P2P) seçti ve oturum başına 2-16 oyuncunun olduğu bir sosyal dedektiflik oyunu için bu gerçekten doğru karardı. İşte nedeni ve takasların nerede ortaya çıktığı.
P2P Maliyet Modeli
Matematiği düşünün. Mütevazı bir bulut örneğinde (paylaşımlı vCPU için ~$0.04/saat) 15 dakika süren 16 oyunculu bir maç için özel bir dedicated server mimarisi maç başına yaklaşık $0.01'e mal olur. Yoğun saatlerde 500.000 eşzamanlı maçla çarpın ve yalnızca işlem gücünde saatte $5.000'e bakıyorsunuz. Bu, günde $120.000 demek.
P2P, bu işlem maliyetini ev sahibi oyuncunun makinesine kaydırır. Altyapı maliyetiniz koordinasyon katmanına düşer: matchmaking sunucuları, lobi durumu, kimlik doğrulama ve STUN/TURN rölesi. Goose Goose Duck için bu, oyuncu sayıları fırlasa bile altyapı faturalarının yönetilebilir kalması anlamına geliyordu.
P2P Güvenilirlik Tavanı
Ancak P2P, özel sunucuların sahip olmadığı hata modlarını beraberinde getirir:
- Host migration: Ev sahibi oyuncunun bağlantısı koptuğunda, oturum yetkiyi başka bir eşe devretmelidir. Bir sosyal dedektiflik oyunu için, kötü bir host migration, kayıp oy durumu, senkronizasyonu bozulmuş rol atamaları ve mahvolmuş bir maç anlamına gelir. Tipik bir host migration dizisi şöyle görünür:
// Basitleştirilmiş eşler arası host migration mantığı
// Mevcut ana bilgisayar erişilemez olduğunda
void OnHostUnreachable(float timeoutSeconds = 3.0f) {
// 1. Tüm eşler kalp atışı zaman aşımıyla ana bilgisayar kopmasını algılar
// 2. Her eş bağımsız olarak yeni ana bilgisayar olup olmayacağını değerlendirir
TArray<FPlayerInfo> remainingPeers = GetConnectedPeers();
FPlayerInfo newHost = SelectNewHost(remainingPeers); // En düşük gecikme, en yüksek bant genişliği
if (newHost.PlayerId == GetLocalPlayerId()) {
// Bu eş yeni ana bilgisayar olur
BecomeHost();
// Yetkili oyun durumunu yerel önbellekten yeniden oluştur
GameState = ReconstructFromLastKnownState();
// Diğer tüm eşlere yeni ana bilgisayara bağlanmalarını söyle
BroadcastHostMigration(newHost.Address);
// Oynanışı sürdür - oylar, zamanlayıcılar ve rol atamaları bu geçişi atlatmalıdır
ResumeSessionWithReconciledState();
} else {
// Geçiş sinyalini bekle, ardından yeni ana bilgisayara bağlan
ConnectToNewHost(newHost.Address, timeoutSeconds);
}
}
Bu adımların her biri potansiyel bir hata noktasıdır. İki eş de ana bilgisayar olmaya karar verirse (bölünmüş beyin senaryosu), uzlaştırılamayacak iki farklı oyun durumu elde edersiniz.
NAT traversal hataları: Simetrik NAT veya operatör düzeyinde NAT arkasındaki oyuncular doğrudan bağlantı kuramaz. TURN röle altyapınız bu oyuncuları absorbe etmelidir. Ölçekte, 700.000 eşzamanlı oyuncunun %15'i, röle trafiği gerektiren 105.000 oyuncudur—ve röle bant genişliği pahalıdır, genellikle GB başına $0.05–$0.10.
Hile koruması zafiyeti: Ev sahibi oyuncunun makinesi yetkilidir. İstemci tarafındaki herhangi bir veri manipüle edilebilir. Sıradan bir parti oyunu için bu, rekabetçi bir nişancı oyununa göre daha az felakettir, ancak yine de deneyimi düşürür. Sunucu odaklı tasarımlar (Fortnite'ın sunucu optimizasyon önerileri gibi) tüm bir istismar kategorisini ortadan kaldırır, ancak özel işlem gücü gerektirirler.
Matchmaking Katmanı: Beklenen Zirvenizin 10 Katı İçin İnşa Etmek
Bu, çoğu indie geliştiricinin ihtiyaç duyduğu ancak çok geç olana kadar atladığı bölümdür. Matchmaking sisteminiz oyununuzun ön kapısıdır. Yavaşsa oyuncular gider. Bozuksa oyuncular oynayamaz.
Lobi Durum Yönetimi Mimarisi
Goose Goose Duck'ın lobi sisteminin ölçekte şu işlemleri halletmesi gerekiyordu:
- Lobilere göz atma (okuma ağırlıklı): Oyuncular mevcut lobileri filtreler ve listeler
- Lobi oluşturma (yazma): Oyun ayarları, bölge ve kapasite içeren yeni bir lobi kaydı
- Lobiye katılma (koşullu yazma): Atomik işlem—kapasiteyi kontrol et, oyuncuyu ekle veya başarısız ol
- Lobiden ayrılma (yazma + olası silme): Oyuncuyu kaldır, boşsa lobiyi sil
- Lobi ayarlarını güncelleme (yazma): Ev sahibi oyun parametrelerini değiştirir
İşte yük altında en çok hataya açık olan atomik katılma işlemini yöneten basitleştirilmiş bir lobi yöneticisi:
import asyncio
from dataclasses import dataclass, field
from typing import Optional
import uuid
@dataclass
class Lobby:
lobby_id: str
host_id: str
max_players: int
players: list = field(default_factory=list)
region: str = "us-east"
game_settings: dict = field(default_factory=dict)
created_at: float = 0.0
class LobbyManager:
def __init__(self, cache_client, db_client):
self.cache = cache_client # Redis veya benzeri
self.db = db_client # PostgreSQL veya benzeri
self.MAX_LOBBIES_PER_REGION = 10000
self.LOBBY_TTL_SECONDS = 3600 # Eski lobileri otomatik temizle
async def join_lobby(self, lobby_id: str, player_id: str) -> dict:
"""
Redis iyimser kilitleme kullanan atomik katılma işlemi.
İki oyuncunun aynı anda tek koltuk kalmış bir lobiye
katılmaya çalıştığı yarış durumunu önler.
"""
cache_key = f"lobby:{lobby_id}"
# Atomik kontrol-ve-değiştirme için Redis'te bir Lua betiği kullan
# Bu kritik yoldur—viral yük altında bu tek işlem
# saniyede binlerce kez çalışır
lua_script = """
local key = KEYS[1]
local player_id = ARGV[1]
local max_players = tonumber(ARGV[2])
local lobby_data = redis.call('HGETALL', key)
if #lobby_data == 0 then
return {-1, "lobby_not_found"}
end
-- Hash'ten oyuncu sayısını ayrıştır
local current_players = tonumber(redis.call('HGET', key, 'player_count'))
if current_players == nil then
return {-1, "corrupted_state"}
end
if current_players >= max_players then
return {0, "lobby_full"}
end
-- Atomik artırma ve oyuncu ekleme
redis.call('HINCRBY', key, 'player_count', 1)
redis.call('SADD', key .. ':players', player_id)
redis.call('EXPIRE', key, 3600)
return {1, "joined"}
"""
result = await self.cache.eval(
lua_script,
keys=[cache_key],
args=[player_id, str(self.MAX_PLAYERS)]
)
status_code, message = result
if status_code == -1:
raise LobbyNotFoundException(message)
elif status_code == 0:
raise LobbyFullException(message)
# Kalıcı DB'ye asenkron yazma (engellemesiz, nihai tutarlılık burada yeterli)
asyncio.create_task(self._persist_join(lobby_id, player_id))
return {"status": "joined", "lobby_id": lobby_id}
async def _persist_join(self, lobby_id: str, player_id: str):
"""Arka plan kalıcılığı—katılmalar için gerçek kaynak Redis'teki lobi durumudur.
DB yalnızca milisaniyelerle geride kalır ancak kritik yolda değildir."""
await self.db.execute(
"UPDATE lobbies SET player_count = player_count + 1, "
"updated_at = NOW() WHERE lobby_id = $1",
lobby_id
)
await self.db.execute(
"INSERT INTO lobby_players (lobby_id, player_id, joined_at) "
"VALUES ($1, $2, NOW()) ON CONFLICT DO NOTHING",
lobby_id, player_id
)
Buradaki kilit detay Redis'teki Lua betiğidir. GET yapan, uygulama kodunda kapasiteyi kontrol eden, ardından POST yapan saf bir uygulama, aynı anda 15 oyuncunun 16 oyunculu bir lobiye katılabileceği bir yarış penceresi oluşturur ve bu da 17 oyuncu ve bozuk oyun mantığıyla sonuçlanır. Lua betiği Redis içinde atomik olarak yürütülür—saniyede binlerce işlemde bile yarış durumu, kayıp katılma olmaz.
Bağlantı Devri: Lobiden Oynanışa
Bir lobi dolduğunda, oyunun merkezi lobi koordinasyonundan eşler arası oynanışa geçiş yapması gerekir. Bu devir, çoğu indie multiplayer oyunun gecikme artışları veya tamamen başarısızlıklar yaşadığı yerdir.
Çalışan desen:
- Ev sahibi oyuncu bir WebSocket veya UDP dinleme soketi açar
- Sunucu (lobi sistemi) ev sahibinin IP ve portunu tüm eşlere dağıtır
- Eşler STUN aracılığıyla doğrudan P2P bağlantısı kurmayı dener
- STUN N saniye içinde başarısız olursa, TURN rölesine düşülür
- Tüm eşler bağlandığını bildirdiğinde, ev sahibi oyun başlangıcını işaret eder
Bu devir sırasında gerçek zamanlı iletişim için WebSocket bağlantıları, HTTP polling'den çok daha güvenilirdir, özellikle bağlantı durumu güncellemelerini aynı anda 8-16 istemciye itmeniz gerektiğinde.
Viral Dalga Sırasında Trafik Şekillendirme
Goose Goose Duck ekibinin yaptığı en akıllı şeylerden biri, yoğun trafik sırasında beklentileri yönetmekti. Backend'iniz kapasiteye ulaştığında iki seçeneğiniz var: her şeyin öngörülemez şekilde bozulmasına izin vermek (rastgele kopmalar, bozuk lobi durumu, zaman aşımı hataları) veya zarif bir bozulma (graceful degradation) uygulamak.
Zarif Bozulma Desenleri
Bağlantı kuyruğu: Lobi sunucuları kapasitedeyken oyuncuları reddetmek yerine, onları gerçek zamanlı bir pozisyon sayacıyla sanal bir kuyruğa koyun. Oyuncular 2 dakika bekleyebilir. Anlaşılmaz bir "sunucu hatası" mesajına tahammül edemezler.
// Pozisyon geri bildirimli C# bağlantı kuyruğu
public class ConnectionQueue
{
private readonly ConcurrentQueue<string> _queue = new();
private readonly SemaphoreSlim _admissionGate;
private readonly int _maxConcurrentSessions;
public ConnectionQueue(int maxConcurrentSessions)
{
_maxConcurrentSessions = maxConcurrentSessions;
_admissionGate = new SemaphoreSlim(maxConcurrentSessions, maxConcurrentSessions);
}
public async Task<QueueResult> TryEnterQueue(string playerId)
{
int position = _queue.Count + 1;
_queue.Enqueue(playerId);
// Tahmini bekleme süresi: mevcut verimde ortalama ~30 saniye oturum arama süresi varsay
int estimatedWaitSeconds = (position / _maxConcurrentSessions) * 30;
if (_admissionGate.CurrentCount > 0)
{
await _admissionGate.WaitAsync();
_queue.TryDequeue(out _);
return new QueueResult { Admitted = true, Position = 0 };
}
return new QueueResult
{
Admitted = false,
Position = position,
EstimatedWaitSeconds = estimatedWaitSeconds
};
}
}
Bölgesel yük atma: ABD-Doğu aşırı yüklüyken AB-Batı kapasiteye sahipse, bağlantıyı tamamen reddetmek yerine yeni ABD'li oyuncuları bir gecikme uyarısıyla AB'ye yönlendirin. Sosyal dedektiflik oyununda 120ms ping neredeyse fark edilmez—bunlar kare mükemmel dövüş oyunları değil.
Lobi oluşturma hız sınırlaması: Yoğun yük sırasında, lobi oluşturmayı oyuncu başına 30 saniyede bir lobiyle sınırlayın. Bu, bot kaynaklı lobi spam'ini (Goose Goose Duck için gerçek bir sorundu) önler ve lobi veritabanındaki yazma baskısını azaltır.
Maliyet Dökümü: Viral Ölçek Aslında Ne Kadara Mal Olur
Buna gerçek rakamlar koyalım. İşte farklı backend mimarileri kullanan Goose Goose Duck ölçeğinde bir viral etkinlik için kaba bir maliyet modeli:
Merkezi lobi koordinasyonlu P2P (Goose Goose Duck'ın yaptığı):
| Bileşen | Aylık Maliyet (700K eşzamanlı zirvede) |
|---|---|
| Lobi/matchmaking sunucuları (12 c5.2xlarge örneği, otomatik ölçeklenen) | $3,500–$5,000 |
| Lobi durumu için Redis kümesi (3 düğüm, r6g.xlarge) | $1,800 |
| TURN röle sunucuları (trafiğin %15'i, ~100K oyuncu) | $8,000–$15,000 |
| Kalıcı durum için PostgreSQL (RDS Multi-AZ) | $600 |
| Bant genişliği (lobi koordinasyonu, günde ~2 TB) | $1,200 |
| Toplam | $15,100–$23,600/ay |
Tamamen dedicated server (her maç bir bulut VM'inde):
| Bileşen | Aylık Maliyet (700K eşzamanlı zirvede) |
|---|---|
| Oyun sunucuları (~50.000 eşzamanlı maç × $0.04/saat) | $1,440,000/ay |
| Matchmaking ve lobi | $5,000 |
| Veritabanı altyapısı | $2,000 |
| Toplam | ~$1,447,000/ay |
Maliyet farkı iki kat. Kozmetik ürünlerle para kazanan ücretsiz bir oyun için dedicated server modeli, birinci günden itibaren agresif bir para kazanma stratejisi yoksa doğrudan iflasa giden yoldur. P2P tembel bir mimari değildir—bilinçli bir finansal karardır.
Ancak tasarruflar takaslarla gelir. Hile ciddiyeti artar. Bağlantı kalitesi ev sahibine göre değişir. Ve koordinasyon altyapınız kurşun geçirmez olmalıdır çünkü oyununuzdaki her maç için tek hata noktasıdır.
Kendi projeniz için bu takasları değerlendiriyorsanız, horizOn koordinasyon katmanını—lobi yönetimi, matchmaking, oyuncu kimlik doğrulama ve oturum durumu—sizin için halleder, böylece altyapı yerine oynanışa odaklanabilirsiniz. Platform, özellikle bu kullanım durumu için oluşturuldu: aylarını backend mühendisliğine ayırmadan ölçeklenmesi gereken indie ekipler.
Viral Büyümeyi Atlatmak İçin 5 Backend Mimarisi Deseni
İşte ihtiyacınız olmadan önce uygulamanız gereken somut desenler; çünkü bunları trafik dalgası sırasında yeniden donatmak, backend yangınlarını backend cenazelerine dönüştürmenin yoludur:
1. Lobi Durumunu Oyun Durumundan Ayırın
Lobi koordinasyon sisteminiz ve gerçek oynanış ağınız, farklı ölçekleme profillerine sahip farklı sistemlerdir. Lobi durumu yüksek okuma, orta düzey yazma gerektirir ve önbelleklemeden (Redis) faydalanır. Oyun durumu yüksek frekanslı, düşük gecikmelidir ve ev sahibi makineye veya özel bir sunucuya aittir. Bunları tek bir veritabanında karıştırmak ölçeklendirme için ölüm tuzağıdır.
2. Kapasiteye Duyarlı Yazmalar İçin Atomik İşlemler Kullanın
Yukarıda gösterdiğim lobiye katılma işlemi, Redis Lua aracılığıyla atomiktir. Bir oyun odasının taşmasını belirleyen herhangi bir şey için uygulama düzeyinde kilitlemeye güvenmeyin. Saniyede 2.000 katılma işleminde, 10 ms'lik bir yarış penceresi bile 20 aşırı satılmış lobi anlamına gelir.
3. İhtiyacınız Olmadan Önce Bağlantı Kuyruğu Uygulayın
60 saniyelik tahmini bekleme süresine sahip bir kuyruk, oyuncuların %70-80'ini elde tutar. Genel bir "bağlantı başarısız oldu" hatası yaklaşık sıfır oyuncu tutar. Kuyruk sistemini ilk mimarinize inşa edin. Trafik düşükken devre dışı bırakabilirsiniz, ancak trafik patladığında yeterince hızlı inşa edemezsiniz.
4. Lobi-Oyun Geçişini Ayrı Olarak İzleyin
Çoğu izleme sistemi "toplam çevrimiçi oyuncu" ve "hata oranı"nı takip eder. Geçiş noktası için belirli metriklerinize ihtiyacınız var: dolu lobilerin yüzde kaçı başarıyla oynanışa geçiyor? Bu sayı %95'in altına düşerse, STUN/TURN altyapınız veya P2P delik delme mantığınız başarısız oluyordur. Bu, oyuncu kaybını diğer tüm metriklerden daha doğru tahmin eden metriktir.
5. Zarif Bir Bozulma Merdiveni İnşa Edin
Bozulma koşullarınızı önceden tanımlayın:
- Yeşil (kapasitenin %80'inin altında): Tam işlevsellik, kısıtlama yok
- Sarı (%80-95 kapasite): Lobi oluşturma hız sınırlamasını etkinleştir, oyuncuları mevcut lobilere katılmaya yönlendir
- Turuncu (%95-100 kapasite): Bağlantı kuyruğunu etkinleştir, matchmaking filtrelerini devre dışı bırak, bölgeler arası maçları kabul et
- Kırmızı (kapasitenin üstünde): Tam kuyruğa alma, yeni bağlantılar için statik yedek sayfa, mevcut oturumlara öncelik ver
Bu eşikleri altyapı yapılandırmanıza yazın. Her sınırda uyarılar ayarlayın. "Viral an" ile "viral felaket" arasındaki fark, Kırmızı'ya düşmeden önce Turuncu'ya ulaşıp ulaşmadığınızdır.
Daha Büyük Ders
Goose Goose Duck hikayesi, multiplayer oyun mimarisi hakkında temel bir şeyi gösteriyor: P2P ile dedicated server arasındaki karar bir kalite kararı değildir—kademeli sonuçları olan ekonomik ve mimari bir karardır. P2P, Gaggle Studios'a potansiyel olarak milyonlarca sunucu maliyeti tasarrufu sağladı, ancak sağlam bir koordinasyon katmanı, dikkatli lobi yönetimi ve belirli kalite takaslarını kabul etme isteği gerektiriyordu.
Multiplayer mimarilerini planlayan indie geliştiriciler için çıkarılacak ders açık: ortalamanıza göre değil, zirvenize göre tasarlayın. Backend'iniz oyununuzun viral olduğu gün normal yükünün 50-100 katını yaşayacaktır. Bu ölçekte test etmediyseniz, hazır değilsiniz.
Lobi ve matchmaking katmanıyla başlayın. Atomik lobi işlemlerini doğru yapın. Bağlantı kuyruğu inşa edin. Bölgesel yedekleme uygulayın. Bunlar, viral anınızın bir başarı hikayesi mi yoksa bir ölüm sonrası mı olacağını belirleyen bileşenlerdir.
Aylarca backend mühendisliğini atlayıp, ölçekte savaş testinden geçmiş bir koordinasyon katmanıyla oyununuzu çıkarmak istiyorsanız, horizOn kutuya hazır lobi yönetimi, matchmaking ve oturum durumu sunar—böylece sunucularınızın dayanıp dayanmayacağını merak etmek yerine oyununuzu eğlenceli hale getirmeye odaklanabilirsiniz. Mimarınıza nasıl uyduğunu görmek için API dökümanlarına göz atın.
Kaynak: Nasıl Dünyanın En Büyük Sosyal Dedektiflik Oyununu Kurduk