From bd376123b3255e584852fef193ca9ea44c228bdf Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Wed, 26 Nov 2025 16:35:19 +0530 Subject: [PATCH] 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`. --- game_flow.py | 30 ++++++++++++++++++++---------- match_making_flow.py | 42 +++++++++++++++++++++--------------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/game_flow.py b/game_flow.py index 188b553..3c9efb3 100644 --- a/game_flow.py +++ b/game_flow.py @@ -118,17 +118,27 @@ class PlayerWebSocketHandler(WebSocketHandler): print(f"[{self.label}] UNKNOWN OPCODE {op}: {payload}") # ---------- Match Helpers ---------- - def rpc_find_match(self) -> str: - """Call rpc_find_match and return Nakama match_id.""" - r = requests.post( - f"{HOST}/v2/rpc/rpc_find_match", - headers={"Authorization": f"Bearer {self.token}"}, - ) - r.raise_for_status() + async def join_matchmaking(self, mode: str = "classic"): + """Queue into Nakama matchmaker.""" + await self.ws.send(json.dumps({ + "matchmaker_add": { + "min_count": 2, + "max_count": 2, + "string_properties": { + "mode": mode + } + } + })) + print(f"[{self.label}] Searching match for mode={mode}...") - # RPC returns {"payload": ""} - payload = json.loads(r.json()["payload"]) - return payload["match_id"] + async def leave_matchmaking(self, ticket: str): + """Remove player from Nakama matchmaking queue.""" + await self.ws.send(json.dumps({ + "matchmaker_remove": { + "ticket": ticket + } + })) + print(f"[{self.label}] Left matchmaking queue") async def create_match(self) -> str: await self.ws.send(json.dumps({"match_create": {}})) diff --git a/match_making_flow.py b/match_making_flow.py index 93ee091..545e478 100644 --- a/match_making_flow.py +++ b/match_making_flow.py @@ -2,10 +2,10 @@ import asyncio 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") - # 1) Login + WebSocket connect (NO listener yet!) + # 1) Login + WebSocket connect players = await asyncio.gather(*[ PlayerWebSocketHandler.setup_player(f"player_{i}") 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") - # 2) First player requests match via RPC - 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) + # 2) Start listeners BEFORE matchmaking for p in players: 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(10) + await asyncio.sleep(0.3) + + # 3) Queue all players in matchmaking + print(f"\n๐ŸŽฏ Queuing players for mode={mode}...\n") - # 6) Cleanup 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__":