diff --git a/game_flow.py b/game_flow.py index 06c754c..6c5d2fa 100644 --- a/game_flow.py +++ b/game_flow.py @@ -2,6 +2,7 @@ import random import asyncio import base64 import json +from typing import Awaitable, Callable import requests import websockets @@ -19,16 +20,23 @@ def print_board(board): print("\n" + separator.join(rows) + "\n") -class PlayerWebSocketHandler(object): - def __init__(self, custom_id: str, label: str): +class WebSocketHandler(object): + def __init__( + self, + custom_id: str, + label: str, + ): self.custom_id = custom_id self.label = label + self.token = None self.ws = None self.listener_task = None - # ---------- Auth & Connect ---------- + async def on_message(self, msg: dict): + raise NotImplementedError("Override me!") + # ---------- Auth & Connect ---------- def login(self): """Authenticate via custom ID and store token.""" basic = base64.b64encode(f"{SERVER_KEY}:".encode()).decode() @@ -50,31 +58,13 @@ class PlayerWebSocketHandler(object): self.ws = await websockets.connect(url) # ---------- Listener ---------- - async def listener(self): """Continuously receive events + decode match data.""" try: while True: raw = await self.ws.recv() msg = json.loads(raw) - - if "match_data" not in msg: - print(f"[{self.label}] {msg}") - continue - - md = msg["match_data"] - op = int(md.get("op_code", 0)) - payload = json.loads(base64.b64decode(md["data"]).decode()) - - if op == 1: - print(f"[{self.label}] MOVE -> {payload}") - elif op == 2: - print(f"\n[{self.label}] GAME STATE") - print_board(payload["board"]) - print(f"Turn={payload['turn']} Winner={payload['winner']}\n") - else: - print(f"[{self.label}] UNKNOWN OPCODE {op}: {payload}") - + await self.on_message(msg) # ✅ handoff except websockets.exceptions.ConnectionClosedOK: print(f"[{self.label}] WebSocket closed gracefully") except websockets.exceptions.ConnectionClosedError as e: @@ -85,8 +75,35 @@ class PlayerWebSocketHandler(object): if self.ws: self.listener_task = asyncio.create_task(self.listener()) - # ---------- Match Helpers ---------- + # ---------- Cleanup ---------- + async def close(self): + if self.listener_task: + self.listener_task.cancel() + if self.ws: + await self.ws.close() + print(f"[{self.label}] Connection closed") + +class PlayerWebSocketHandler(WebSocketHandler): + async def on_message(self, msg): + if "match_data" not in msg: + print(f"[{self.label}] {msg}") + return + + md = msg["match_data"] + op = int(md.get("op_code", 0)) + payload = json.loads(base64.b64decode(md["data"]).decode()) + + if op == 1: + print(f"[{self.label}] MOVE -> {payload}") + elif op == 2: + print(f"\n[{self.label}] GAME STATE") + print_board(payload["board"]) + print(f"Turn={payload['turn']} Winner={payload['winner']}\n") + else: + print(f"[{self.label}] UNKNOWN OPCODE {op}: {payload}") + + # ---------- Match Helpers ---------- async def create_match(self) -> str: await self.ws.send(json.dumps({"match_create": {}})) msg = json.loads(await self.ws.recv()) @@ -100,7 +117,6 @@ class PlayerWebSocketHandler(object): print(f"[{self.label}] Joined match: {match_id}") # ---------- Gameplay ---------- - async def send_move(self, match_id: str, row: int, col: int): payload = {"row": row, "col": col} encoded = base64.b64encode(json.dumps(payload).encode()).decode() @@ -114,15 +130,6 @@ class PlayerWebSocketHandler(object): })) print(f"[{self.label}] Sent move: {payload}") - # ---------- Cleanup ---------- - - async def close(self): - if self.listener_task: - self.listener_task.cancel() - if self.ws: - await self.ws.close() - print(f"[{self.label}] Connection closed") - async def happy_path( match_id: str,