import socket import threading import pickle import pygame import sys import time import math HOST = '127.0.0.1' PORT = 65432 ACCELERATION = 300.0 BRAKE_DECELERATION = 400.0 FRICTION = 200.0 TURN_SPEED = math.radians(120) MAX_SPEED = 400.0 MIN_SPEED = -200.0 CAR_WIDTH = 40 CAR_HEIGHT = 20 class Event: def __init__(self, name, payload): self.name = name self.payload = payload def recvall(sock, n): data = b'' while len(data) < n: packet = sock.recv(n - len(data)) if not packet: return None data += packet return data def send_event(sock, event): data = pickle.dumps(event) length = len(data) sock.sendall(length.to_bytes(4, byteorder='big') + data) def recv_event(sock): raw_len = recvall(sock, 4) if not raw_len: return None msg_len = int.from_bytes(raw_len, byteorder='big') data = recvall(sock, msg_len) return pickle.loads(data) players = {} # client_id -> state from server my_client_id = None predicted_state = { "x": 100, "y": 100, "angle": 0, "speed": 0, "keys": set(), "username": "Guest", "checkpoint_index": 0, "finished": False } server_details = {} map_data = {} # Will hold the map (roads, obstacles, checkpoints, finish lines) ping = 0 local_keys = set() def listen_to_server(sock): global my_client_id, players, predicted_state, ping, server_details, map_data while True: try: event = recv_event(sock) if event is None: break if event.name == "self_id": if my_client_id is None: my_client_id = event.payload.get("client_id") print(f"[INFO] My client ID: {my_client_id}") elif event.name == "server_info": server_details = event.payload map_data = server_details.get("map", {}) print(f"[INFO] Received server info: {server_details}") elif event.name == "state_update": players = event.payload.get("players", {}) elif event.name == "race_reset": predicted_state.update({ "x": 100, "y": 100, "angle": 0, "speed": 0, "keys": set(), "checkpoint_index": 0, "finished": False }) print("[RACE] Race reset by server.") elif event.name == "pong": sent_time = event.payload.get("timestamp") if sent_time: round_trip = time.time() - sent_time ping = round_trip print(f"[PING] {ping*1000:.0f} ms") except Exception as e: print(f"[ERROR] {e}") break def ping_loop(sock): while True: ts = time.time() send_event(sock, Event("ping", {"timestamp": ts})) time.sleep(1) def draw_car(surface, x, y, angle, color): half_w = CAR_WIDTH / 2 half_h = CAR_HEIGHT / 2 corners = [(-half_w, -half_h), (half_w, -half_h), (half_w, half_h), (-half_w, half_h)] rotated = [] cos_a = math.cos(angle) sin_a = math.sin(angle) for cx, cy in corners: rx = cx * cos_a - cy * sin_a ry = cx * sin_a + cy * cos_a rotated.append((int(x + rx), int(y + ry))) pygame.draw.polygon(surface, color, rotated) def main(): global my_client_id, predicted_state username = input("Enter your username: ") predicted_state["username"] = username sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((HOST, PORT)) threading.Thread(target=listen_to_server, args=(sock,), daemon=True).start() threading.Thread(target=ping_loop, args=(sock,), daemon=True).start() for event_name in ["client_connect", "client_disconnect", "state_update", "race_reset"]: send_event(sock, Event("register_event", {"event": event_name})) print(f"[INFO] Registered for event: {event_name}") send_event(sock, Event("set_username", {"username": username})) pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("Top Down Racing Multiplayer") clock = pygame.time.Clock() while True: dt = clock.tick(60) / 1000.0 for ev in pygame.event.get(): if ev.type == pygame.QUIT: pygame.quit() sys.exit() elif ev.type == pygame.KEYDOWN: key_name = None if ev.key == pygame.K_LEFT: key_name = "left" elif ev.key == pygame.K_RIGHT: key_name = "right" elif ev.key == pygame.K_UP: key_name = "up" elif ev.key == pygame.K_DOWN: key_name = "down" if key_name and key_name not in local_keys: local_keys.add(key_name) predicted_state["keys"].add(key_name) send_event(sock, Event("keydown", {"key": key_name})) elif ev.type == pygame.KEYUP: key_name = None if ev.key == pygame.K_LEFT: key_name = "left" elif ev.key == pygame.K_RIGHT: key_name = "right" elif ev.key == pygame.K_UP: key_name = "up" elif ev.key == pygame.K_DOWN: key_name = "down" if key_name and key_name in local_keys: local_keys.remove(key_name) if key_name in predicted_state["keys"]: predicted_state["keys"].remove(key_name) send_event(sock, Event("keyup", {"key": key_name})) if "up" in predicted_state["keys"]: predicted_state["speed"] += ACCELERATION * dt elif "down" in predicted_state["keys"]: predicted_state["speed"] -= BRAKE_DECELERATION * dt else: if predicted_state["speed"] > 0: predicted_state["speed"] -= FRICTION * dt if predicted_state["speed"] < 0: predicted_state["speed"] = 0 elif predicted_state["speed"] < 0: predicted_state["speed"] += FRICTION * dt if predicted_state["speed"] > 0: predicted_state["speed"] = 0 if predicted_state["speed"] > MAX_SPEED: predicted_state["speed"] = MAX_SPEED if predicted_state["speed"] < MIN_SPEED: predicted_state["speed"] = MIN_SPEED if "left" in predicted_state["keys"]: predicted_state["angle"] -= TURN_SPEED * dt if "right" in predicted_state["keys"]: predicted_state["angle"] += TURN_SPEED * dt predicted_state["x"] += predicted_state["speed"] * math.cos(predicted_state["angle"]) * dt predicted_state["y"] += predicted_state["speed"] * math.sin(predicted_state["angle"]) * dt local_x = predicted_state["x"] local_y = predicted_state["y"] screen_width, screen_height = 800, 600 camera_offset = (local_x - screen_width // 2, local_y - screen_height // 2) screen.fill((30, 30, 30)) if server_details.get("map"): mdata = server_details["map"] if "road" in mdata: for road in mdata["road"]: rect = pygame.Rect(road["x"] - camera_offset[0], road["y"] - camera_offset[1], road["width"], road["height"]) pygame.draw.rect(screen, (80,80,80), rect) if "obstacle" in mdata: for obs in mdata["obstacle"]: rect = pygame.Rect(obs["x"] - camera_offset[0], obs["y"] - camera_offset[1], obs["width"], obs["height"]) pygame.draw.rect(screen, (150,50,50), rect) if "checkpoint" in mdata: for cp in mdata["checkpoint"]: rect = pygame.Rect(cp["x"] - camera_offset[0], cp["y"] - camera_offset[1], cp["width"], cp["height"]) pygame.draw.rect(screen, (200,200,0), rect, 2) if "finish_line" in mdata: for fl in mdata["finish_line"]: rect = pygame.Rect(fl["x"] - camera_offset[0], fl["y"] - camera_offset[1], fl["width"], fl["height"]) pygame.draw.rect(screen, (0,0,255), rect, 2) for cid, data in players.items(): px = data.get("x", 100) py = data.get("y", 100) angle = data.get("angle", 0) uname = data.get("username", "Guest") if cid == my_client_id: px = predicted_state["x"] py = predicted_state["y"] angle = predicted_state["angle"] color = (0, 0, 255) else: color = (255, 0, 0) draw_car(screen, px - camera_offset[0], py - camera_offset[1], angle, color) font = pygame.font.SysFont(None, 20) name_surface = font.render(uname, True, (255, 255, 255)) screen.blit(name_surface, (px - camera_offset[0] - name_surface.get_width()//2, py - camera_offset[1] - 30)) ping_font = pygame.font.SysFont(None, 24) ping_surface = ping_font.render(f"Ping: {int(ping*1000)} ms", True, (255, 255, 0)) screen.blit(ping_surface, (10, 10)) hud_font = pygame.font.SysFont(None, 24) total_users = server_details.get("total_users", len(players)) max_users = server_details.get("max_users", "?") tickrate = server_details.get("tickrate", 64) hud_text = hud_font.render( f"Users: {total_users}/{max_users} | Client ID: {my_client_id or 'N/A'} | Tickrate: {tickrate} tps", True, (255, 255, 255) ) screen.blit(hud_text, (10, 40)) pygame.display.flip() if __name__ == "__main__": main()