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:
30
game_flow.py
30
game_flow.py
@@ -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": {}}))
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
Reference in New Issue
Block a user