refactor(matchmaking): migrate Python simulator to native Nakama matchmaker

### Summary
Replaced legacy RPC-based matchmaking flow with proper WebSocket-driven
matchmaker integration. Player simulation now queues via
`matchmaker_add`, auto-joins matches on `matchmaker_matched`, and no
longer depends on `rpc_find_match`.
This commit is contained in:
2025-11-26 16:35:19 +05:30
parent ea1a70b212
commit bd376123b3
2 changed files with 41 additions and 31 deletions

View File

@@ -118,17 +118,27 @@ class PlayerWebSocketHandler(WebSocketHandler):
print(f"[{self.label}] UNKNOWN OPCODE {op}: {payload}") print(f"[{self.label}] UNKNOWN OPCODE {op}: {payload}")
# ---------- Match Helpers ---------- # ---------- Match Helpers ----------
def rpc_find_match(self) -> str: async def join_matchmaking(self, mode: str = "classic"):
"""Call rpc_find_match and return Nakama match_id.""" """Queue into Nakama matchmaker."""
r = requests.post( await self.ws.send(json.dumps({
f"{HOST}/v2/rpc/rpc_find_match", "matchmaker_add": {
headers={"Authorization": f"Bearer {self.token}"}, "min_count": 2,
) "max_count": 2,
r.raise_for_status() "string_properties": {
"mode": mode
}
}
}))
print(f"[{self.label}] Searching match for mode={mode}...")
# RPC returns {"payload": "<json string>"} async def leave_matchmaking(self, ticket: str):
payload = json.loads(r.json()["payload"]) """Remove player from Nakama matchmaking queue."""
return payload["match_id"] await self.ws.send(json.dumps({
"matchmaker_remove": {
"ticket": ticket
}
}))
print(f"[{self.label}] Left matchmaking queue")
async def create_match(self) -> str: async def create_match(self) -> str:
await self.ws.send(json.dumps({"match_create": {}})) await self.ws.send(json.dumps({"match_create": {}}))

View File

@@ -2,10 +2,10 @@ import asyncio
from game_flow import PlayerWebSocketHandler from game_flow import PlayerWebSocketHandler
async def simulate_matchmaking(num_players: int = 6): async def simulate_matchmaking(num_players: int = 6, mode: str = "classic"):
print(f"\n🎮 Spawning {num_players} players...\n") print(f"\n🎮 Spawning {num_players} players...\n")
# 1) Login + WebSocket connect (NO listener yet!) # 1) Login + WebSocket connect
players = await asyncio.gather(*[ players = await asyncio.gather(*[
PlayerWebSocketHandler.setup_player(f"player_{i}") PlayerWebSocketHandler.setup_player(f"player_{i}")
for i in range(num_players) for i in range(num_players)
@@ -13,33 +13,33 @@ async def simulate_matchmaking(num_players: int = 6):
print("\n✅ All players authenticated + connected\n") print("\n✅ All players authenticated + connected\n")
# 2) First player requests match via RPC # 2) Start listeners BEFORE matchmaking
p1 = players[0]
match_id = p1.rpc_find_match()
print(f"\n🎯 Match allocated: {match_id}\n")
# 3) Everyone joins the same match
await asyncio.gather(*[
p.join_match(match_id) for p in players
])
print("\n✅ All players joined match\n")
# 4) NOW start websocket listeners (prevents recv race)
for p in players: for p in players:
p.start_listener() p.start_listener()
print("\n👂 Listening for match state + updates...\n") print("\n👂 WebSocket listeners active\n")
# 5) Keep test running long enough to observe messages await asyncio.sleep(0.3)
await asyncio.sleep(10)
# 3) Queue all players in matchmaking
print(f"\n🎯 Queuing players for mode={mode}...\n")
# 6) Cleanup
await asyncio.gather(*[ await asyncio.gather(*[
p.close() for p in players p.join_matchmaking(mode)
for p in players
]) ])
print("\n🏁 Simulation complete\n") print("\n✅ All players queued — waiting for matches...\n")
# 4) Allow enough time for Nakama matchmaker to group players
await asyncio.sleep(12)
# 5) Cleanup
print("\n🧹 Closing player connections...\n")
await asyncio.gather(*[p.close() for p in players])
print("\n🏁 Matchmaking simulation complete\n")
if __name__ == "__main__": if __name__ == "__main__":