import json import socket import sys import threading # Establishes connections to TCP clients. def listen() -> None: lobby_manager = LobbyManager() threads = [] while True: try: connection, address = tcp_socket.accept() new_thread = threading.Thread(target=start_listen_client, args=(connection, address, lobby_manager)) new_thread.start() threads.append(new_thread) except socket.timeout: pass except KeyboardInterrupt: tcp_socket.close() def start_listen_client(connection, address, lobby_manager): ClientConnection(connection, address, lobby_manager) class Lobby: def __init__(self, lobby_id, host_client_id): self.lobby_id = lobby_id self.host_client_id = host_client_id self.connected_clients = {} class Client: def __init__(self, client_id, client_name, address, connection): self.client_id = client_id self.client_name = client_name self.address = address self.connection = connection class LobbyManager: def __init__(self): self.active_lobbies = {} def get_lobby(self, lobby_id) -> Lobby: return self.active_lobbies[lobby_id] def create_lobby(self, lobby_id, host_client_id) -> None: self.active_lobbies[lobby_id] = Lobby(lobby_id, host_client_id) print(f"Created lobby {lobby_id}") def delete_lobby(self, lobby_id) -> None: try: del self.active_lobbies[lobby_id] print(f"Deleted lobby {lobby_id}") except KeyError: print(f"Lobby with ID {lobby_id} not found, could not be deleted") def join_lobby(self, lobby_id, client_id, client_name, address, connection) -> None: self.active_lobbies[lobby_id].connected_clients[client_id] = Client( client_id, client_name, address, connection, ) print(f"Client {client_id} joined lobby {lobby_id}") self.broadcast_message( lobby_id, json.dumps({ "response": "client_joined_lobby", "new_client_id": client_id, "new_client_name": client_name, }) ) def leave_lobby(self, lobby_id, client_id) -> None: try: del self.active_lobbies[lobby_id].connected_clients[client_id] self.broadcast_message( lobby_id, json.dumps({ "response": "leave_lobby", "status": "ok", "client_id": client_id, }), ) if self.get_lobby_player_count(lobby_id) == 0: self.delete_lobby(lobby_id) print(f"Deleted client {client_id} from lobby {lobby_id}") except KeyError: print(f"Client with ID {client_id} in lobby {lobby_id} not found, could not be deleted") def get_lobby_player_count(self, lobby_id) -> int: return len(self.active_lobbies[lobby_id].connected_clients) def get_lobby_host_client_id(self, lobby_id) -> str: return self.active_lobbies[lobby_id].host_client_id def send_message(self, lobby_id, client_id, message) -> None: self.active_lobbies[lobby_id].connected_clients[client_id].connection.sendall(str.encode(message)) def broadcast_message(self, lobby_id, message) -> None: connected_clients = self.active_lobbies[lobby_id].connected_clients for client_id in connected_clients: connected_clients[client_id].connection.sendall(str.encode(message)) class ClientConnection: def __init__(self, connection, address, lobby_manager): self.connection = connection self.address = address self.lobby_manager = lobby_manager self.listen_client() # Listens to a connected TCP client. def listen_client(self) -> None: try: print(f"connected to {self.address[0]}:{self.address[1]}") received_data = "" while True: data = self.connection.recv(1) received_data += data.decode("utf-8") print(received_data) if received_data[-1] == "}": self.process_input(received_data) received_data = "" if not data: break except IndexError: print("Client force-disconnected") finally: self.connection.close() print(f"Closing connection to {self.address[0]}:{self.address[1]}") def process_input(self, data: str) -> None: data_object = json.loads(data) if data_object["response"] == "create_lobby": print(data_object["client_id"] + " is creating lobby " + data_object["lobby_id"]) self.lobby_manager.create_lobby( data_object["lobby_id"], data_object["client_id"], ) self.lobby_manager.join_lobby( data_object["lobby_id"], data_object["client_id"], data_object["client_name"], self.address, self.connection, ) self.lobby_manager.send_message( data_object["lobby_id"], data_object["client_id"], json.dumps({ "response": "create_lobby", "status": "ok", }), ) elif data_object["response"] == "join_lobby": print("joining lobby") self.lobby_manager.join_lobby( data_object["lobby_id"], data_object["client_id"], data_object["client_name"], self.address, self.connection, ) self.lobby_manager.send_message( data_object["lobby_id"], data_object["client_id"], json.dumps({ "response": "join_lobby", "status": "ok", }), ) # send message to all connected clients elif data_object["response"] == "leave_lobby": print("leaving lobby") lobby_id = data_object["lobby_id"] host_client_id = self.lobby_manager.get_lobby_host_client_id(lobby_id) if host_client_id == data_object["client_id"]: # client is host; close lobby lobby_clients = self.lobby_manager.get_lobby(lobby_id).connected_clients.copy() # for client_id in lobby_clients: self.lobby_manager.broadcast_message( lobby_id, json.dumps({ "response": "leave_lobby_request", }) ) # self.lobby_manager.leave_lobby(lobby_id, client_id) # self.lobby_manager.delete_lobby(lobby_id) else: # client is not host; client leaves only self.lobby_manager.leave_lobby(lobby_id, data_object["client_id"]) if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: ./server.py PORT") sys.exit(1) port = int(sys.argv[1]) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('localhost', port) tcp_socket.bind(server_address) tcp_socket.listen(1) tcp_socket.settimeout(0.5) print('Listening on *:%d' % (port)) listen()