इसे छोड़कर कंटेंट पर जाएं

मल्टीप्लेयर आर्किटेक्चर

En Parlant~ मल्टीप्लेयर एक WebSocket रिले सर्वर का उपयोग करता है जो दो खिलाड़ियों को रियल टाइम में जोड़ता है। यहाँ कोई पीयर-टू-पीयर नहीं है — सारा संचार रिले के माध्यम से होता है। इससे नेटवर्किंग सरल रहती है और फ़ायरवॉल तथा NAT के पीछे भी विश्वसनीय रूप से काम करती है।

आर्किटेक्चर अवलोकन

Section titled “आर्किटेक्चर अवलोकन”
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 ----------->|

रिले एक पतली फ़ॉरवर्डिंग परत है। यह मेमोरी में रूम रखता है, प्रत्येक रूम में दो खिलाड़ियों के बीच इवेंट्स को रूट करता है, और क्लीनअप संभालता है। सर्वर पर कोई गेम लॉजिक या मूव वैलिडेशन नहीं है — क्लाइंट्स ही प्राधिकारी (authoritative) हैं।

  • फ़्रंटएंड (क्लाइंट): socket.io-client — मानक Socket.IO JavaScript क्लाइंट
  • बैकएंड (रिले): socketioxideAxum पर बना एक Rust Socket.IO सर्वर
  • प्रोटोकॉल: WebSocket पर Socket.IO (आवश्यकता पड़ने पर HTTP लॉन्ग-पोलिंग में स्वचालित फ़ॉलबैक के साथ)

Socket.IO को रॉ WebSocket के बजाय इसलिए चुना गया क्योंकि यह स्वचालित रीकनेक्शन, रूम/नेमस्पेस प्रबंधन, और संरचित इवेंट हैंडलिंग बॉक्स से बाहर ही प्रदान करता है।

होस्ट Multiplayer पर क्लिक करता है और अपना डिस्प्ले नाम दर्ज करता है। क्लाइंट नाम के साथ एक create_game इवेंट भेजता है। सर्वर:

  1. एक अद्वितीय 6-अक्षर का रूम कोड जनरेट करता है
  2. एक रूम बनाता है और होस्ट को पहले खिलाड़ी के रूप में जोड़ता है
  3. game_created(code) के साथ जवाब देता है ताकि होस्ट कोड साझा कर सके

2. गेम में शामिल होना

Section titled “2. गेम में शामिल होना”

शामिल होने वाला खिलाड़ी रूम कोड और अपना डिस्प्ले नाम दर्ज करता है। क्लाइंट join_game(code, name) भेजता है। सर्वर:

  1. कोड द्वारा रूम खोजता है
  2. शामिल होने वाले को दूसरे खिलाड़ी के रूप में जोड़ता है
  3. दोनों खिलाड़ियों को game_joined(name) भेजता है — प्रत्येक को दूसरे का डिस्प्ले नाम प्राप्त होता है

एक बार दोनों खिलाड़ी रूम में आ जाएँ:

  • होस्ट हमेशा सफ़ेद खेलता है, शामिल होने वाला हमेशा काला खेलता है। यह शामिल होने के क्रम से निर्धारित होता है, बातचीत से नहीं।
  • मूव UCI फ़ॉर्मेट में भेजे जाते हैं (जैसे, e2e4) दोनों खिलाड़ियों के क्लॉक समय के साथ।
  • रिले प्रत्येक game_move इवेंट को प्रतिद्वंद्वी को फ़ॉरवर्ड करता है। दोनों खिलाड़ियों को पुष्टि के रूप में अपने स्वयं के मूव भी वापस मिलते हैं।

4. गेम-समाप्ति इवेंट्स

Section titled “4. गेम-समाप्ति इवेंट्स”

कई इवेंट्स गेम समाप्ति की स्थितियों को संभालते हैं:

  • हार मानना (Resign) — प्रतिद्वंद्वी को फ़ॉरवर्ड किया जाता है ताकि हार मानने का डायलॉग दिखे
  • ड्रॉ प्रस्ताव (Draw offer) — प्रतिद्वंद्वी को फ़ॉरवर्ड किया जाता है, जो स्वीकार या अनदेखा कर सकता है
  • ड्रॉ स्वीकृति (Draw accept) — दोनों खिलाड़ियों को फ़ॉरवर्ड किया जाता है ताकि गेम ड्रॉ के रूप में समाप्त हो

गेम समाप्त होने के बाद, कोई भी खिलाड़ी रीमैच की इच्छा व्यक्त करने के लिए ready इवेंट भेज सकता है। जब दोनों खिलाड़ी ready भेज दें, तो क्लाइंट्स बोर्ड रीसेट करते हैं और रंग बदलते हैं (या बनाए रखते हैं — यह क्लाइंट-साइड निर्धारित होता है)।

रूम कोड 6 अक्षर के होते हैं, मौखिक रूप से साझा करते समय पठनीयता के लिए XX-XX-XX प्रारूप में। अक्षर सेट दृश्य रूप से अस्पष्ट अक्षरों को बाहर रखता है:

  • कोई 0 या O नहीं (शून्य बनाम अक्षर O)
  • कोई 1, I, या L नहीं (एक बनाम बड़ा I बनाम बड़ा L)

इससे “यह शून्य है या O?” वाली समस्या से बचा जा सकता है जब कोई वॉइस चैट पर कोड पढ़ता है या किसी मित्र के संदेश से टाइप करता है।

हार्टबीट सिस्टम

Section titled “हार्टबीट सिस्टम”

मल्टीप्लेयर कनेक्शन में यह पता लगाना आवश्यक है कि कोई खिलाड़ी कब डिस्कनेक्ट हो गया — चाहे नेटवर्क ड्रॉप से, ऐप बंद करने से, या लैपटॉप के स्लीप मोड में जाने से। En Parlant~ इसके लिए हार्टबीट सिस्टम का उपयोग करता है:

  1. प्रत्येक क्लाइंट सर्वर को हर 5 सेकंड में एक heartbeat इवेंट भेजता है
  2. सर्वर हार्टबीट की पुष्टि करता है और इसे प्रतिद्वंद्वी को peer_heartbeat के रूप में फ़ॉरवर्ड करता है
  3. क्लाइंट ट्रैक करता है कि उसे प्रतिद्वंद्वी से आखिरी peer_heartbeat कब मिला था
  4. isPeerAlive(timeoutMs) फ़ंक्शन जाँचता है कि प्रतिद्वंद्वी का आखिरी हार्टबीट स्वीकार्य सीमा के भीतर है या नहीं

यह UI में कनेक्शन स्थिति संकेतक को संचालित करता है। यदि हार्टबीट आना बंद हो जाएँ, तो खिलाड़ी देखता है कि उसका प्रतिद्वंद्वी शायद डिस्कनेक्ट हो गया है, और वह प्रतीक्षा करने या गेम छोड़ने का विकल्प चुन सकता है।

रिले सर्वर मेमोरी लीक रोकने के लिए निष्क्रिय रूम को स्वचालित रूप से हटाता है:

  • किसी भी गतिविधि के बिना 30 मिनट के बाद रूम को निष्क्रिय माना जाता है
  • एक क्लीनअप टास्क हर 60 सेकंड में चलता है, निष्क्रिय सीमा पार कर चुके रूम को साफ़ करता है
  • जब कोई रूम साफ़ किया जाता है, तो शेष सभी कनेक्शन ड्रॉप कर दिए जाते हैं

कोई स्थायी स्टोरेज नहीं है। यदि सर्वर पुनः प्रारंभ होता है, तो सभी रूम समाप्त हो जाते हैं। यह जानबूझकर है — रिले स्टेटलेस और क्षणिक है। चल रहे गेम को पुनः शुरू करना होगा, लेकिन व्यवहार में ऐसा शायद ही होता है।

डिफ़ॉल्ट रिले Fly.io पर चलता है, जो स्वचालित TLS के साथ कम-विलंबता WebSocket कनेक्शन प्रदान करता है। अपना खुद का रिले चलाने के निर्देशों के लिए मल्टीप्लेयर सर्वर सेटअप गाइड देखें।

स्थानीय रूप से परीक्षण

Section titled “स्थानीय रूप से परीक्षण”

विकास के दौरान मल्टीप्लेयर का परीक्षण करने के लिए:

  1. रिले को क्लोन करें और चलाएँ:

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

    सर्वर पोर्ट 3210 पर शुरू होता है।

  2. En Parlant~ में, रिले सर्वर URL को ws://localhost:3210 में बदलें।

  3. दोनों खिलाड़ियों का अनुकरण करने के लिए ऐप के दो इंस्टेंस खोलें (या एक ऐप और एक डेव मोड में)।

मूव, हार्टबीट, और सभी इवेंट्स प्रोडक्शन रिले के समान ही काम करते हैं — एकमात्र अंतर कनेक्शन URL है।