Event_Based_Server/server.py

243 lines
8.8 KiB
Python
Raw Normal View History

2025-03-25 16:47:59 +00:00
import socket
import threading
import pickle
2025-03-25 16:47:59 +00:00
import time
import uuid
2025-03-26 23:35:34 +00:00
import math
2025-03-25 16:47:59 +00:00
HOST = '127.0.0.1'
PORT = 65432
2025-03-26 23:35:34 +00:00
TICKRATE = 128 # ticks per second (sub-tick simulation)
MAX_USERS = 100
2025-03-31 15:14:04 +00:00
# Racing physics constants.
ACCELERATION = 300.0 # pixels per second^2 (forward)
BRAKE_DECELERATION = 400.0 # pixels per second^2 (when braking)
FRICTION = 200.0 # pixels per second^2 (natural deceleration)
TURN_SPEED = math.radians(120) # radians per second (steering)
MAX_SPEED = 400.0 # maximum forward speed (pixels/sec)
MIN_SPEED = -200.0 # maximum reverse speed (pixels/sec)
2025-03-26 23:35:34 +00:00
2025-03-31 15:14:04 +00:00
# Player appearance (for collision).
PLAYER_RADIUS = 20
2025-03-26 23:35:34 +00:00
# Define map obstacles.
map_obstacles = [
{"x": 200, "y": 150, "width": 100, "height": 300},
{"x": 500, "y": 100, "width": 50, "height": 400},
{"x": 100, "y": 500, "width": 600, "height": 50},
]
def circle_rect_collision(cx, cy, radius, rect):
closest_x = max(rect["x"], min(cx, rect["x"] + rect["width"]))
closest_y = max(rect["y"], min(cy, rect["y"] + rect["height"]))
dx = cx - closest_x
dy = cy - closest_y
2025-03-31 15:14:04 +00:00
return (dx * dx + dy * dy) < (radius * radius)
2025-03-26 23:35:34 +00:00
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)
2025-03-25 16:47:59 +00:00
2025-03-26 23:35:34 +00:00
# Global dictionaries.
event_subscriptions = {} # event name -> list of client sockets
2025-03-25 16:47:59 +00:00
client_ids = {} # conn -> client_id
2025-03-31 15:14:04 +00:00
# Each client state now stores position, angle, speed, and keys.
client_states = {} # conn -> {"client_id", "username", "x", "y", "angle", "speed", "keys"}
2025-03-25 16:47:59 +00:00
lock = threading.Lock()
def handle_client(conn, addr):
client_id = str(uuid.uuid4())
with lock:
client_ids[conn] = client_id
client_states[conn] = {
"client_id": client_id,
"username": "Guest",
"x": 100,
"y": 100,
2025-03-31 15:14:04 +00:00
"angle": 0, # Facing right initially.
"speed": 0,
"keys": set()
}
2025-03-25 16:47:59 +00:00
print(f"[NEW CONNECTION] {addr} connected as {client_id}")
try:
send_event(conn, Event("self_id", {"client_id": client_id}))
server_info = {
"tickrate": TICKRATE,
"total_users": len(client_states),
"max_users": MAX_USERS,
"server_ip": HOST,
"server_port": PORT,
2025-03-31 15:14:04 +00:00
"server_name": "My Top Down Racing Server",
2025-03-26 23:35:34 +00:00
"map": map_obstacles
}
send_event(conn, Event("server_info", server_info))
2025-03-25 16:47:59 +00:00
except Exception as e:
print(f"[ERROR] sending self_id/server_info: {e}")
2025-03-25 16:47:59 +00:00
broadcast_event("client_connect", {"client_id": client_id})
2025-03-25 16:47:59 +00:00
try:
while True:
event = recv_event(conn)
if event is None:
break
if event.name == "register_event":
event_name = event.payload.get('event')
with lock:
event_subscriptions.setdefault(event_name, []).append(conn)
print(f"[REGISTER] {client_id} subscribed to '{event_name}'")
elif event.name == "set_username":
username = event.payload.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 event.name == "keydown":
key = event.payload.get('key')
with lock:
if conn in client_states:
client_states[conn]['keys'].add(key)
print(f"[KEYDOWN] {client_id} key '{key}' pressed")
elif event.name == "keyup":
key = event.payload.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 event.name == "ping":
timestamp = event.payload.get('timestamp')
send_event(conn, Event("pong", {"timestamp": timestamp}))
2025-03-31 15:14:04 +00:00
# Any other events (e.g., mouse_move, shoot) are ignored for racing.
2025-03-25 16:47:59 +00:00
except Exception as e:
2025-03-26 23:35:34 +00:00
print(f"[ERROR] Exception with {client_ids.get(conn, 'unknown')}: {e}")
2025-03-25 16:47:59 +00:00
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.")
broadcast_event("client_disconnect", {"client_id": client_id})
2025-03-25 16:47:59 +00:00
conn.close()
def broadcast_event(event_name, payload):
event = Event(event_name, payload)
2025-03-25 16:47:59 +00:00
with lock:
receivers = event_subscriptions.get(event_name, [])
for client in receivers[:]:
2025-03-25 16:47:59 +00:00
try:
send_event(client, event)
2025-03-25 16:47:59 +00:00
except Exception as e:
print(f"[ERROR] Failed to send to client, removing: {e}")
receivers.remove(client)
def game_loop():
2025-03-26 23:35:34 +00:00
dt = 1 / TICKRATE
2025-03-25 16:47:59 +00:00
while True:
time.sleep(dt)
with lock:
2025-03-31 15:14:04 +00:00
# Update each player's state using racing physics.
2025-03-25 16:47:59 +00:00
for state in client_states.values():
2025-03-31 15:14:04 +00:00
# Acceleration or braking.
if 'up' in state['keys']:
state['speed'] += ACCELERATION * dt
elif 'down' in state['keys']:
state['speed'] -= BRAKE_DECELERATION * dt
else:
# Apply friction when no acceleration is active.
if state['speed'] > 0:
state['speed'] -= FRICTION * dt
if state['speed'] < 0:
state['speed'] = 0
elif state['speed'] < 0:
state['speed'] += FRICTION * dt
if state['speed'] > 0:
state['speed'] = 0
# Clamp speed.
if state['speed'] > MAX_SPEED:
state['speed'] = MAX_SPEED
if state['speed'] < MIN_SPEED:
state['speed'] = MIN_SPEED
# Steering.
2025-03-25 16:47:59 +00:00
if 'left' in state['keys']:
2025-03-31 15:14:04 +00:00
state['angle'] -= TURN_SPEED * dt
2025-03-25 16:47:59 +00:00
if 'right' in state['keys']:
2025-03-31 15:14:04 +00:00
state['angle'] += TURN_SPEED * dt
# Compute new position.
new_x = state['x'] + state['speed'] * math.cos(state['angle']) * dt
new_y = state['y'] + state['speed'] * math.sin(state['angle']) * dt
2025-03-26 23:35:34 +00:00
# Check collision with obstacles.
2025-03-31 15:14:04 +00:00
collision = False
2025-03-26 23:35:34 +00:00
for obs in map_obstacles:
2025-03-31 15:14:04 +00:00
if circle_rect_collision(new_x, new_y, PLAYER_RADIUS, obs):
collision = True
2025-03-26 23:35:34 +00:00
break
2025-03-31 15:14:04 +00:00
if not collision:
state['x'] = new_x
state['y'] = new_y
2025-03-26 23:35:34 +00:00
else:
2025-03-31 15:14:04 +00:00
# On collision, stop the car.
state['speed'] = 0
2025-03-26 23:35:34 +00:00
2025-03-31 15:14:04 +00:00
# Build payload to send to all clients.
2025-03-25 16:47:59 +00:00
players_payload = {}
for state in client_states.values():
players_payload[state['client_id']] = {
"username": state['username'],
"x": state['x'],
"y": state['y'],
2025-03-31 15:14:04 +00:00
"angle": state['angle'],
"speed": state['speed']
2025-03-25 16:47:59 +00:00
}
total_users = len(client_states)
payload = {
"players": players_payload,
"total_users": total_users,
"max_users": MAX_USERS
}
broadcast_event("state_update", payload)
2025-03-25 16:47:59 +00:00
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()