পাইথন প্রজেক্টস ও লাইব্রেরি

৪৬. মাল্টিপ্লেয়ার অনলাইন গেম (Multiplayer Game with WebSockets)

আপনি হয়তো পাইথন এবং Pygame দিয়ে একা একা খেলার মতো গেম বানাতে পারেন। কিন্তু যদি এমন গেম বানাতে চান যেখানে আপনি আপনার বাসা থেকে খেলবেন আর আপনার বন্ধু তার বাসা থেকে খেলবে (যেমন: পাবজি বা লুডু), তখন কী করবেন?

মাল্টিপ্লেয়ার গেম বানানোর জন্য দরকার রিয়েল-টাইম (Real-time) ডেটা ট্রান্সফার। সাধারণ ওয়েবসাইট যেভাবে কাজ করে (HTTP Request), গেম সেভাবে কাজ করে না। গেমের জন্য আমাদের WebSockets ব্যবহার করতে হয়, যা মিলি-সেকেন্ডের মধ্যে সার্ভার থেকে প্লেয়ারদের কাছে ডেটা আদান-প্রদান করতে পারে।

কীভাবে কাজ করে? (How it works):

  1. WebSocket Server: প্রথমে আমরা ক্লাউডে বা ইন্টারনেটে একটি সার্ভার তৈরি করবো। সব প্লেয়ার ওই সার্ভারে কানেক্ট হবে।
  2. Client/Player: আপনি যখন গেমে আপনার ক্যারেক্টারকে সামনে আগাবেন (Move), তখন আপনার গেম (Client) সার্ভারকে একটি মেসেজ পাঠাবে যে "আমি X, Y পজিশনে আছি"।
  3. Broadcasting: সার্ভার সাথে সাথে ওই মেসেজটি আপনার বন্ধুর গেমে (Client) পাঠিয়ে দিবে। ফলে আপনার বন্ধু দেখতে পাবে যে আপনি সামনে আগাচ্ছেন। পুরো বিষয়টি মিলি-সেকেন্ডের মধ্যে ঘটবে!

প্রয়োজনীয় লাইব্রেরি (Libraries):

টার্মিনালে নিচের কমান্ডটি লিখে ওয়েব-সকেট লাইব্রেরিটি ইনস্টল করে নিন:

pip install websockets

(বিঃদ্রঃ গেমের গ্রাফিক্সের জন্য Pygame ব্যবহৃত হলেও, নিচে আমরা শুধু মাল্টিপ্লেয়ারের কোর নেটওয়ার্কিং লজিকটি দেখাচ্ছি।)

প্রজেক্টের কোড (সার্ভার সাইড):

নিচের কোডটি হলো আমাদের গেম সার্ভার। এটি সব প্লেয়ারের কানেকশন ধরে রাখবে এবং একজনের মুভমেন্ট (Movement) অন্যজনকে জানিয়ে দিবে। কোডটি server.py নামে সেভ করে রান করতে হয়।

import asyncio
import websockets
import json

# বর্তমানে গেমে কতজন প্লেয়ার কানেক্টেড আছে তার লিস্ট
connected_players = set()

# প্লেয়ারদের বর্তমান পজিশন সেভ করে রাখার ডিকশনারি
player_positions = {}

async def game_handler(websocket, path):
    """যখনই কোনো নতুন প্লেয়ার গেমে ঢুকবে, এই ফাংশনটি কল হবে"""
    
    # প্লেয়ারকে গেমে অ্যাড করা
    connected_players.add(websocket)
    player_id = str(id(websocket))[-4:] # প্লেয়ারের একটি ৪-ডিজিটের আইডি
    print(f"[+] Player {player_id} joined the game!")
    
    try:
        # প্লেয়ারের কাছ থেকে কন্টিনিউয়াস ডেটা (যেমন: পজিশন) রিসিভ করা
        async for message in websocket:
            # প্লেয়ারের পাঠানো JSON ডেটা রিড করা (যেমন: {"x": 100, "y": 200})
            data = json.loads(message)
            
            # সার্ভারে প্লেয়ারের নতুন পজিশন আপডেট করা
            player_positions[player_id] = {"x": data["x"], "y": data["y"]}
            
            # সার্ভার এখন সব প্লেয়ারের পজিশন মিলিয়ে একটি মেসেজ বানাবে
            game_state = json.dumps(player_positions)
            
            # ব্রডকাস্ট (Broadcast): এই নতুন আপডেটটি গেমে থাকা বাকি সব প্লেয়ারকে পাঠিয়ে দেওয়া
            for player in connected_players:
                if player != websocket: # নিজেকে নিজের পজিশন পাঠানোর দরকার নেই
                    await player.send(game_state)
                    
    except websockets.exceptions.ConnectionClosed:
        print(f"[-] Player {player_id} disconnected.")
        
    finally:
        # প্লেয়ার গেম থেকে বের হয়ে গেলে তাকে সার্ভার থেকে মুছে ফেলা
        connected_players.remove(websocket)
        if player_id in player_positions:
            del player_positions[player_id]

async def start_server():
    print("=== Multiplayer Game Server Started ===")
    print("Waiting for players to connect on port 8765...")
    
    # লোকালহোস্টে 8765 পোর্টে সার্ভারটি চালু করা
    async with websockets.serve(game_handler, "localhost", 8765):
        await asyncio.Future()  # সার্ভারটি যেন অসীম সময় ধরে চলতে থাকে

if __name__ == "__main__":
    # ইভেন্ট লুপের মাধ্যমে সার্ভারটি রান করা
    asyncio.run(start_server())

কোডটি কীভাবে শিখবেন?

  1. asyncio এবং async/await: মাল্টিপ্লেয়ার গেমে সার্ভারকে একই সাথে অনেকগুলো কাজ করতে হয়। একজন প্লেয়ার হয়তো গুলি করছে, আরেকজন হয়তো দৌড়াচ্ছে। সাধারণ ফাংশন দিয়ে এই কাজ করলে গেম আটকে (Lag) যাবে। তাই asyncio (Asynchronous I/O) ব্যবহার করা হয়েছে, যা একই সাথে হাজার হাজার কাজ প্যারালালি (Parallel) করতে পারে।
  2. WebSocket Connection: websockets.serve(...) এর মাধ্যমে একটি রিয়েল-টাইম হাইওয়ে (Highway) তৈরি হয়। HTTP রিকোয়েস্টে যেমন একবার ডেটা নিয়ে কানেকশন কেটে যায়, ওয়েব-সকেট তা করে না। এটি গেম চলাকালীন পুরোটা সময় লাইভ কানেকশন ধরে রাখে।
  3. JSON Serialization: আমরা যখন এক কম্পিউটার থেকে আরেক কম্পিউটারে ডেটা পাঠাই, তখন তা পাইথনের ডিকশনারি হিসেবে সরাসরি পাঠানো যায় না। তাই json.dumps() ব্যবহার করে পজিশন ডেটাগুলোকে একটি স্ট্রিং বা টেক্সটে রূপান্তর করে পাঠানো হয়েছে। ক্লায়েন্ট সাইডে গেমটি রিসিভ করার পর আবার json.loads() দিয়ে তা ডিকশনারিতে রূপান্তর করে নিবে।

[!TIP] গেম ক্লায়েন্ট (Client): এই সার্ভারটির সাথে যুক্ত হওয়ার জন্য আপনি websockets.connect("ws://localhost:8765") ব্যবহার করে একটি ক্লায়েন্ট স্ক্রিপ্ট তৈরি করতে পারেন, যা Pygame এর সাথে যুক্ত হয়ে রিয়েল-টাইম মাল্টিপ্লেয়ার গেমের রূপ নিবে!