İçeriğe geç

Çok Oyunculu Mimari

En Parlant~ çok oyunculu modu, iki oyuncuyu gerçek zamanlı olarak bağlamak için bir WebSocket aktarım sunucusu kullanır. Eşler arası (peer-to-peer) bağlantı yoktur — tüm iletişim aktarım sunucusu üzerinden gerçekleşir. Bu, ağ yapısını basit tutar ve güvenlik duvarları ile NAT’lar arkasında güvenilir şekilde çalışır.

Player A (host) Relay Server Player B (joiner)
| | |
|--- create_game(name) ----------->| |
|<-- game_created(code) -----------| |
| |<--- join_game(code, name) -----|
|<-- game_joined(name) ------------|--- game_joined(name) -------->|
| | |
|--- game_move(uci, times) ------->|--- game_move(uci, times) --->|
|<-- game_move(uci, times) --------|<-- game_move(uci, times) ----|
| | |
|--- heartbeat ------------------>|--- peer_heartbeat ----------->|

Aktarım sunucusu ince bir yönlendirme katmanıdır. Odaları bellekte tutar, her odadaki iki oyuncu arasında olayları yönlendirir ve temizlik işlemlerini yönetir. Sunucuda oyun mantığı veya hamle doğrulaması yoktur — istemciler yetkilidir.

  • Ön yüz (istemci): socket.io-client — standart Socket.IO JavaScript istemcisi
  • Arka yüz (aktarım): socketioxideAxum üzerine inşa edilmiş bir Rust Socket.IO sunucusu
  • Protokol: WebSocket üzerinden Socket.IO (gerektiğinde otomatik olarak HTTP long-polling’e geri dönüş ile)

Socket.IO, ham WebSocket yerine tercih edilmiştir çünkü otomatik yeniden bağlanma, oda/ad alanı yönetimi ve yapılandırılmış olay işleme özelliklerini kutudan çıktığı gibi sunar.

Ev sahibi Multiplayer seçeneğine tıklar ve görünen adını girer. İstemci, adla birlikte bir create_game olayı gönderir. Sunucu:

  1. Benzersiz 6 karakterlik bir oda kodu oluşturur
  2. Bir oda oluşturur ve ev sahibini ilk oyuncu olarak ekler
  3. Ev sahibinin kodu paylaşabilmesi için game_created(code) ile yanıt verir

Katılımcı oda kodunu ve görünen adını girer. İstemci join_game(code, name) olayını gönderir. Sunucu:

  1. Koda göre odayı bulur
  2. Katılımcıyı ikinci oyuncu olarak ekler
  3. Her iki oyuncuya da game_joined(name) gönderir — her biri diğerinin görünen adını alır

Her iki oyuncu da odada olduğunda:

  • Ev sahibi her zaman Beyaz, katılımcı her zaman Siyah oynar. Bu, müzakere ile değil katılım sırasına göre belirlenir.
  • Hamleler UCI formatında (örn. e2e4) gönderilir ve her iki oyuncunun saat süreleri de birlikte iletilir.
  • Aktarım sunucusu her game_move olayını rakibe iletir. Her iki oyuncu da onay olarak kendi hamlelerini geri alır.

Oyun sonu senaryolarını birkaç olay yönetir:

  • İstifa — terk etme diyalogunu tetiklemek için rakibe iletilir
  • Beraberlik teklifi — rakibe iletilir; rakip kabul edebilir veya görmezden gelebilir
  • Beraberlik kabulü — oyunu beraberlik olarak sonlandırmak için her iki oyuncuya iletilir

Bir oyun sona erdikten sonra, herhangi bir oyuncu rövanş isteğini bildirmek için ready olayı gönderebilir. Her iki oyuncu da ready gönderdiğinde, istemciler tahtayı sıfırlar ve renkleri değiştirir (veya aynı tutar — istemci tarafında belirlenir).

Oda kodları 6 karakterdir ve sözlü paylaşımda okunabilirlik için XX-XX-XX biçiminde formatlanır. Karakter seti görsel olarak karıştırılabilecek karakterleri hariç tutar:

  • 0 veya O yok (sıfır ile O harfi karışıklığı)
  • 1, I veya L yok (bir ile büyük I ve büyük L karışıklığı)

Bu, birisi sesli sohbet üzerinden bir kodu okurken veya arkadaşının mesajından yazarken yaşanabilecek “bu sıfır mı yoksa O mu?” sorununu önler.

Çok oyunculu bağlantılarda bir oyuncunun bağlantısının kesildiğini tespit etmek gerekir — ister ağ kesintisinden, ister uygulamanın kapatılmasından, isterse dizüstü bilgisayarın uyku moduna geçmesinden kaynaklansın. En Parlant~ bunun için bir kalp atışı sistemi kullanır:

  1. Her istemci, sunucuya her 5 saniyede bir heartbeat olayı gönderir
  2. Sunucu kalp atışını onaylar ve peer_heartbeat olarak rakibe iletir
  3. İstemci, rakipten en son ne zaman peer_heartbeat aldığını takip eder
  4. isPeerAlive(timeoutMs) fonksiyonu, rakibin son kalp atışının kabul edilebilir eşik dahilinde olup olmadığını kontrol eder

Bu, kullanıcı arayüzündeki bağlantı durumu göstergesini yönlendirir. Kalp atışları gelmeyi bırakırsa, oyuncu rakibinin bağlantısının kopmuş olabileceğini görür ve beklemeyi veya oyundan ayrılmayı seçebilir.

Aktarım sunucusu, bellek sızıntılarını önlemek için boşta kalan odaları otomatik olarak kaldırır:

  • Bir oda, 30 dakika boyunca herhangi bir etkinlik olmadığında boşta kabul edilir
  • Her 60 saniyede bir temizlik görevi çalışır ve boşta kalma eşiğini aşmış odaları temizler
  • Bir oda temizlendiğinde, kalan tüm bağlantılar düşürülür

Kalıcı depolama yoktur. Sunucu yeniden başlarsa tüm odalar kaybolur. Bu kasıtlıdır — aktarım sunucusu durumsuz ve geçicidir. Devam eden oyunların yeniden başlatılması gerekir, ancak pratikte bu nadiren yaşanır.

Varsayılan aktarım sunucusu Fly.io üzerinde çalışır ve otomatik TLS ile düşük gecikmeli WebSocket bağlantıları sağlar. Kendi aktarım sunucunuzu çalıştırmak için talimatları Çok Oyunculu Sunucu kurulum kılavuzunda bulabilirsiniz.

Geliştirme sırasında çok oyunculu modu test etmek için:

  1. Aktarım sunucusunu klonlayın ve çalıştırın:

    Terminal window
    git clone https://github.com/DarrellThomas/en-parlant-relay.git
    cd en-parlant-relay
    cargo run

    Sunucu 3210 portunda başlar.

  2. En Parlant~ uygulamasında aktarım sunucusu URL’sini ws://localhost:3210 olarak değiştirin.

  3. Her iki oyuncuyu simüle etmek için uygulamanın iki örneğini açın (veya bir uygulama ve bir geliştirme modunda).

Hamleler, kalp atışları ve tüm olaylar üretim aktarım sunucusuyla aynı şekilde çalışır — tek fark bağlantı URL’sidir.