Event_Based_Server/server.py
2025-03-25 11:47:59 -05:00

150 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 clients 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 clients 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()