import socketio from datetime import datetime from app.services.room_manager import room_manager from app.services.game_manager import game_manager def register_socket_events(sio: socketio.AsyncServer): """Register all Socket.IO event handlers.""" @sio.event async def connect(sid, environ): print(f"Client connected: {sid}") @sio.event async def disconnect(sid): print(f"Client disconnected: {sid}") # Remove player from room room = await room_manager.remove_player(sid) if room: await sio.emit( "player_left", {"room": room}, room=room["code"] ) @sio.event async def create_room(sid, data): """Create a new game room.""" player_name = data.get("player_name", "Player") room = await room_manager.create_room(player_name, sid) # Join socket room sio.enter_room(sid, room["code"]) await sio.emit("room_created", {"room": room}, to=sid) @sio.event async def join_room(sid, data): """Join an existing room.""" room_code = data.get("room_code", "").upper() player_name = data.get("player_name", "Player") team = data.get("team", "A") room = await room_manager.add_player(room_code, player_name, team, sid) if not room: await sio.emit( "error", {"message": "Could not join room. It may be full or the name is taken."}, to=sid ) return # Join socket room sio.enter_room(sid, room_code) # Notify all players await sio.emit("player_joined", {"room": room}, room=room_code) @sio.event async def change_team(sid, data): """Switch player to another team.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] new_team = data.get("team") room = await room_manager.get_room(room_code) if not room or len(room["teams"][new_team]) >= 4: await sio.emit( "error", {"message": "Cannot change team. It may be full."}, to=sid ) return # Remove from current team current_team = player["team"] room["teams"][current_team] = [ p for p in room["teams"][current_team] if p["socket_id"] != sid ] # Add to new team room["teams"][new_team].append({ "name": player["name"], "team": new_team, "position": len(room["teams"][new_team]), "socket_id": sid }) await room_manager.update_room(room_code, room) await sio.emit("team_changed", {"room": room}, room=room_code) @sio.event async def start_game(sid, data): """Start the game (host only).""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] room = await room_manager.get_room(room_code) if not room: return # Check if player is host if room["host"] != player["name"]: await sio.emit( "error", {"message": "Only the host can start the game."}, to=sid ) return # Check minimum players if not room["teams"]["A"] or not room["teams"]["B"]: await sio.emit( "error", {"message": "Both teams need at least one player."}, to=sid ) return # Get board from data or generate board = data.get("board", {}) updated_room = await game_manager.start_game(room_code, board) if updated_room: await sio.emit("game_started", {"room": updated_room}, room=room_code) @sio.event async def select_question(sid, data): """Select a question from the board.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] question_id = data.get("question_id") category_id = data.get("category_id") room = await game_manager.select_question(room_code, question_id, category_id) if room: # Get current player info current_player = await game_manager.get_current_player(room) await sio.emit( "question_selected", { "room": room, "question_id": question_id, "current_player": current_player }, room=room_code ) @sio.event async def submit_answer(sid, data): """Submit an answer to the current question.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] answer = data.get("answer", "") question = data.get("question", {}) is_steal = data.get("is_steal", False) result = await game_manager.submit_answer( room_code, question, answer, is_steal ) if "error" in result: await sio.emit("error", {"message": result["error"]}, to=sid) return await sio.emit( "answer_result", { "player_name": player["name"], "team": player["team"], "answer": answer, "valid": result["valid"], "reason": result["reason"], "points_earned": result["points_earned"], "was_steal": is_steal, "room": result["room"] }, room=room_code ) @sio.event async def steal_decision(sid, data): """Decide whether to attempt stealing.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] attempt = data.get("attempt", False) question_id = data.get("question_id") if not attempt: # Pass on steal room = await game_manager.pass_steal(room_code, question_id) if room: await sio.emit( "steal_passed", {"room": room, "team": player["team"]}, room=room_code ) else: # Will attempt steal - just notify, answer comes separately room = await room_manager.get_room(room_code) await sio.emit( "steal_attempted", { "team": player["team"], "player_name": player["name"], "room": room }, room=room_code ) @sio.event async def chat_message(sid, data): """Send a chat message to team.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] message = data.get("message", "")[:500] # Limit message length # Get all team members' socket IDs room = await room_manager.get_room(room_code) if not room: return team_sockets = [ p["socket_id"] for p in room["teams"][player["team"]] ] # Send only to team members for socket_id in team_sockets: await sio.emit( "chat_message", { "player_name": player["name"], "team": player["team"], "message": message, "timestamp": datetime.utcnow().isoformat() }, to=socket_id ) @sio.event async def emoji_reaction(sid, data): """Send an emoji reaction visible to all.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] emoji = data.get("emoji", "") # Validate emoji allowed_emojis = ["👏", "😮", "😂", "🔥", "💀", "🎉", "😭", "🤔"] if emoji not in allowed_emojis: return await sio.emit( "emoji_reaction", { "player_name": player["name"], "team": player["team"], "emoji": emoji }, room=room_code ) @sio.event async def timer_expired(sid, data): """Handle timer expiration.""" player = await room_manager.get_player(sid) if not player: return room_code = player["room"] room = await room_manager.get_room(room_code) if not room: return # Treat as wrong answer if room["can_steal"]: # Steal timer expired - pass question_id = room["current_question"] room = await game_manager.pass_steal(room_code, question_id) await sio.emit("time_up", {"room": room, "was_steal": True}, room=room_code) else: # Answer timer expired - enable steal room["can_steal"] = True room["current_team"] = "B" if room["current_team"] == "A" else "A" await room_manager.update_room(room_code, room) await sio.emit("time_up", {"room": room, "was_steal": False}, room=room_code)