import socket import threading import json import time import uuid HOST = '127.0.0.1' PORT = 65432 # Global dictionaries for event subscriptions, client IDs, and game state. event_subscriptions = {} # event_name -> list of client sockets client_ids = {} # conn -> client_id client_states = {} # conn -> {"client_id", "username", "x", "y", "keys"} lock = threading.Lock() MOVE_SPEED = 200 # pixels per second def handle_client(conn, addr): client_id = str(uuid.uuid4()) with lock: client_ids[conn] = client_id # Initialize state: default username "Guest", starting position, and an empty set of pressed keys. client_states[conn] = {"client_id": client_id, "username": "Guest", "x": 100, "y": 100, "keys": set()} print(f"[NEW CONNECTION] {addr} connected as {client_id}") # Send a dedicated "self_id" event so the client knows its unique id. try: msg = json.dumps({'event': 'self_id', 'payload': {'client_id': client_id}}) + "\n" conn.sendall(msg.encode()) except Exception as e: print(f"[ERROR] sending self_id: {e}") # Broadcast that a new client connected. send_event("client_connect", {"client_id": client_id}) try: file_obj = conn.makefile('r') for line in file_obj: line = line.strip() if not line: continue try: msg = json.loads(line) msg_type = msg.get('type') if msg_type == 'register_event': event_name = msg.get('event') with lock: event_subscriptions.setdefault(event_name, []).append(conn) print(f"[REGISTER] {client_id} subscribed to '{event_name}'") elif msg_type == 'set_username': username = msg.get('username', 'Guest') with lock: if conn in client_states: client_states[conn]['username'] = username print(f"[USERNAME] {client_id} set username to '{username}'") elif msg_type == 'keydown': key = msg.get('key') with lock: if conn in client_states: client_states[conn]['keys'].add(key) print(f"[KEYDOWN] {client_id} key '{key}' pressed") elif msg_type == 'keyup': key = msg.get('key') with lock: if conn in client_states and key in client_states[conn]['keys']: client_states[conn]['keys'].remove(key) print(f"[KEYUP] {client_id} key '{key}' released") elif msg_type == 'ping': # Respond immediately with a pong, echoing the timestamp. timestamp = msg.get('timestamp') response = json.dumps({'event': 'pong', 'payload': {'timestamp': timestamp}}) + "\n" conn.sendall(response.encode()) # Additional message types can be handled here. except json.JSONDecodeError: print(f"[ERROR] Invalid JSON from {client_id}: {line}") except Exception as e: print(f"[ERROR] Exception with {client_id}: {e}") finally: with lock: for subs in event_subscriptions.values(): if conn in subs: subs.remove(conn) client_ids.pop(conn, None) client_states.pop(conn, None) print(f"[DISCONNECT] {client_id} disconnected.") send_event("client_disconnect", {"client_id": client_id}) conn.close() def send_event(event_name, payload): """ Broadcast an event to all clients subscribed to the given event. """ message = json.dumps({'event': event_name, 'payload': payload}) + "\n" with lock: receivers = event_subscriptions.get(event_name, []) for client in receivers[:]: # iterate over a copy to allow removals try: client.sendall(message.encode()) except Exception as e: print(f"[ERROR] Failed to send to client, removing: {e}") receivers.remove(client) def game_loop(): """ Main game loop: update each client’s position based on currently pressed keys, and broadcast the overall game state (including server time) to all clients. """ dt = 0.1 #100 ms; while True: time.sleep(dt) with lock: # Update each client's position. for state in client_states.values(): dx, dy = 0, 0 if 'left' in state['keys']: dx -= MOVE_SPEED * dt if 'right' in state['keys']: dx += MOVE_SPEED * dt if 'up' in state['keys']: dy -= MOVE_SPEED * dt if 'down' in state['keys']: dy += MOVE_SPEED * dt state['x'] += dx state['y'] += dy # Build payload: include current server time and each client’s state. players_payload = {} for state in client_states.values(): players_payload[state['client_id']] = { "username": state['username'], "x": state['x'], "y": state['y'] } payload = {"server_time": time.time(), "players": players_payload} send_event("state_update", payload) def start_server(): print("[STARTING] Server is starting...") threading.Thread(target=game_loop, daemon=True).start() with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() print(f"[LISTENING] Server is listening on {HOST}:{PORT}") while True: conn, addr = s.accept() threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start() if __name__ == "__main__": start_server()