Improve client matchmaking flow with ticket handling, auto-join, and mode distribution

### PlayerWebSocketHandler updates
- Track `ticket` and `match_id` per player instance
- Handle `matchmaker_ticket` messages and store ticket
- Handle `matchmaker_matched` and automatically join created match
- Enhance matchmaking debug output
- Update join_matchmaking() to include mode-based string_properties + query

### Matchmaking simulation improvements
- Evenly distribute players between "classic" and "blitz" modes
- Randomize assignment order to simulate real queue behavior
- Log player→mode mapping for visibility during tests

Example:
  player_0 -> classic
  player_3 -> blitz
  player_5 -> classic

Client test harness now accurately reflects multi-mode matchmaking behavior.
This commit is contained in:
2025-11-26 17:11:20 +05:30
parent eb35ccd180
commit 10058710fb
2 changed files with 33 additions and 8 deletions

View File

@@ -96,7 +96,23 @@ class PlayerWebSocketHandler(WebSocketHandler):
await handler.connect() await handler.connect()
return handler return handler
def __init__(self, custom_id: str, label: str):
super().__init__(custom_id, label)
self.ticket = None
self.match_id = None
async def on_message(self, msg): async def on_message(self, msg):
if "matchmaker_matched" in msg:
match_id = msg["matchmaker_matched"]["match_id"]
print(f"[{self.label}] ✅ Match found: {match_id}")
await self.join_match(match_id)
return
if "matchmaker_ticket" in msg:
self.ticket = msg["matchmaker_ticket"]["ticket"]
print(f"[{self.label}] ✅ Received ticket: {self.ticket}")
return
if "match_data" not in msg: if "match_data" not in msg:
print(f"[{self.label}] {msg}") print(f"[{self.label}] {msg}")
return return
@@ -124,9 +140,8 @@ class PlayerWebSocketHandler(WebSocketHandler):
"matchmaker_add": { "matchmaker_add": {
"min_count": 2, "min_count": 2,
"max_count": 2, "max_count": 2,
"string_properties": { "string_properties": {"mode": mode},
"mode": mode "query": f"+mode:{mode}"
}
} }
})) }))
print(f"[{self.label}] Searching match for mode={mode}...") print(f"[{self.label}] Searching match for mode={mode}...")
@@ -151,6 +166,7 @@ class PlayerWebSocketHandler(WebSocketHandler):
async def join_match(self, match_id: str): async def join_match(self, match_id: str):
await self.ws.send(json.dumps({"match_join": {"match_id": match_id}})) await self.ws.send(json.dumps({"match_join": {"match_id": match_id}}))
print(f"[{self.label}] Joined match: {match_id}") print(f"[{self.label}] Joined match: {match_id}")
self.match_id = match_id
# ---------- Gameplay ---------- # ---------- Gameplay ----------
async def send_move(self, match_id: str, row: int, col: int): async def send_move(self, match_id: str, row: int, col: int):

View File

@@ -1,8 +1,9 @@
import asyncio import asyncio
from game_flow import PlayerWebSocketHandler import random
from game_flow import PlayerWebSocketHandler, TEST_SCENARIOS
async def simulate_matchmaking(num_players: int = 6, mode: str = "classic"): async def simulate_matchmaking(num_players: int = 6):
print(f"\n🎮 Spawning {num_players} players...\n") print(f"\n🎮 Spawning {num_players} players...\n")
# 1) Login + WebSocket connect # 1) Login + WebSocket connect
@@ -21,12 +22,20 @@ async def simulate_matchmaking(num_players: int = 6, mode: str = "classic"):
await asyncio.sleep(0.3) await asyncio.sleep(0.3)
# 3) Queue all players in matchmaking # 3) Split evenly between classic & blitz
print(f"\n🎯 Queuing players for mode={mode}...\n") half = num_players // 2
assignments = ["classic"] * half + ["blitz"] * (num_players - half)
# Optional — shuffle for realism
random.shuffle(assignments)
print("\n🎯 Queuing players:")
for p, mode in zip(players, assignments):
print(f" - {p.label} -> {mode}")
await asyncio.gather(*[ await asyncio.gather(*[
p.join_matchmaking(mode) p.join_matchmaking(mode)
for p in players for p, mode in zip(players, assignments)
]) ])
print("\n✅ All players queued — waiting for matches...\n") print("\n✅ All players queued — waiting for matches...\n")