2025-07-06 19:49:08 +02:00
|
|
|
import json
|
|
|
|
|
import random
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
import ssl
|
|
|
|
|
import asyncio
|
|
|
|
|
from websockets.asyncio.server import serve
|
|
|
|
|
|
2025-07-07 10:57:39 +02:00
|
|
|
MAX_LOBBY_CLIENT_COUNT = 6
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Generates a 6-character code from the letters A-Z
|
2025-07-06 19:49:08 +02:00
|
|
|
def generate_code() -> str:
|
|
|
|
|
result = ""
|
|
|
|
|
for i in range(6):
|
|
|
|
|
result += chr(random.randint(0, 25) + 65)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
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 = {}
|
2025-07-08 10:42:34 +02:00
|
|
|
self.client_playing_order = []
|
2025-07-09 16:48:45 +02:00
|
|
|
self.client_bets = {} # client_id: bet
|
|
|
|
|
# self.currently_playing_client = 0 # index of client_playing_order
|
2025-07-06 19:49:08 +02:00
|
|
|
|
|
|
|
|
class Client:
|
|
|
|
|
|
|
|
|
|
def __init__(self, client_id, client_name, websocket):
|
|
|
|
|
self.client_id = client_id
|
|
|
|
|
self.client_name = client_name
|
|
|
|
|
self.websocket = websocket
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Class that holds active lobbies and manages communication and information exchange
|
2025-07-06 19:49:08 +02:00
|
|
|
class LobbyManager:
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.active_lobbies = {}
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
#region lobby moderation
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Creates a new lobby and assigns a host client
|
2025-07-06 19:49:08 +02:00
|
|
|
def create_lobby(self, lobby_id, host_client_id) -> None:
|
2025-07-07 10:31:22 +02:00
|
|
|
# Add lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
self.active_lobbies[lobby_id] = Lobby(lobby_id, host_client_id)
|
|
|
|
|
print(f"Created lobby {lobby_id}")
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Joins a client to an existing lobby
|
2025-07-07 10:57:39 +02:00
|
|
|
async def join_lobby(self, lobby_id, client_id, client_name, websocket) -> str:
|
|
|
|
|
if not self.lobby_id_exists(lobby_id):
|
|
|
|
|
# Cannot join; lobby does not exist
|
|
|
|
|
return "lobby_not_found"
|
|
|
|
|
|
|
|
|
|
if self.get_lobby_player_count(lobby_id) >= MAX_LOBBY_CLIENT_COUNT:
|
|
|
|
|
# Lobby full! Cannot join
|
|
|
|
|
return "lobby_full"
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Add client to lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
self.active_lobbies[lobby_id].connected_clients[client_id] = Client(
|
|
|
|
|
client_id,
|
|
|
|
|
client_name,
|
|
|
|
|
websocket,
|
|
|
|
|
)
|
|
|
|
|
print(f"Client {client_id} joined lobby {lobby_id}")
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Notify the lobby of the newly-joined client
|
2025-07-06 19:49:08 +02:00
|
|
|
await self.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "client_joined_lobby",
|
|
|
|
|
"new_client_id": client_id,
|
|
|
|
|
"new_client_name": client_name,
|
2025-07-07 22:40:46 +02:00
|
|
|
"connected_client_ids": self.get_lobby_client_ids(lobby_id),
|
2025-07-07 09:58:34 +02:00
|
|
|
"connected_client_names": self.get_lobby_client_names(lobby_id),
|
2025-07-06 19:49:08 +02:00
|
|
|
})
|
|
|
|
|
)
|
2025-07-07 10:57:39 +02:00
|
|
|
|
|
|
|
|
return "ok"
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Removes a client from a lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
async def leave_lobby(self, lobby_id, client_id) -> None:
|
|
|
|
|
try:
|
2025-07-07 10:31:22 +02:00
|
|
|
# Get client name before deleting them
|
|
|
|
|
client_name = self.get_lobby_client_name(lobby_id, client_id)
|
|
|
|
|
|
|
|
|
|
# Delete client
|
2025-07-06 19:49:08 +02:00
|
|
|
del self.active_lobbies[lobby_id].connected_clients[client_id]
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
# Notify remaining clients of leaving client and remaining clients
|
2025-07-06 19:49:08 +02:00
|
|
|
await self.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "client_left_lobby",
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"client_name": client_name,
|
2025-07-07 22:40:46 +02:00
|
|
|
"connected_client_ids": self.get_lobby_client_ids(lobby_id),
|
2025-07-07 09:58:34 +02:00
|
|
|
"connected_client_names": self.get_lobby_client_names(lobby_id),
|
2025-07-06 19:49:08 +02:00
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
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")
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Deletes a lobby. May only be called after ensuring that all clients have left
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region lobby info
|
|
|
|
|
|
|
|
|
|
# Get lobby by its ID
|
|
|
|
|
def get_lobby(self, lobby_id) -> Lobby:
|
|
|
|
|
return self.active_lobbies[lobby_id]
|
|
|
|
|
|
|
|
|
|
# Checks if a lobby exists under the given ID
|
|
|
|
|
def lobby_id_exists(self, lobby_id) -> bool:
|
|
|
|
|
return lobby_id in self.active_lobbies
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region lobby client info
|
|
|
|
|
|
|
|
|
|
# Get count of clients currently connected to a lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
def get_lobby_player_count(self, lobby_id) -> int:
|
|
|
|
|
return len(self.active_lobbies[lobby_id].connected_clients)
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Get name of a client connected to a given lobby
|
|
|
|
|
def get_lobby_client_name(self, lobby_id, client_id) -> str:
|
2025-07-06 19:49:08 +02:00
|
|
|
return self.active_lobbies[lobby_id].connected_clients[client_id].client_name
|
2025-07-07 09:58:34 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Get names of all clients connected to a given lobby
|
2025-07-07 09:58:34 +02:00
|
|
|
def get_lobby_client_names(self, lobby_id) -> list[str]:
|
|
|
|
|
names = []
|
|
|
|
|
lobby = self.get_lobby(lobby_id)
|
|
|
|
|
for client_id in lobby.connected_clients:
|
|
|
|
|
names.append(lobby.connected_clients[client_id].client_name)
|
|
|
|
|
return names
|
2025-07-07 22:40:46 +02:00
|
|
|
|
|
|
|
|
def get_lobby_client_ids(self, lobby_id) -> list[str]:
|
|
|
|
|
return list(self.get_lobby(lobby_id).connected_clients.keys())
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
# Checks if a client is connected to a given lobby under the given ID
|
|
|
|
|
def client_id_exists_in_lobby(self, lobby_id, client_id) -> bool:
|
|
|
|
|
if not self.lobby_id_exists(lobby_id):
|
|
|
|
|
return False
|
|
|
|
|
return client_id in self.active_lobbies[lobby_id].connected_clients
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Gets the ID of the host client of a given lobby
|
|
|
|
|
def get_lobby_host_client_id(self, lobby_id) -> str:
|
|
|
|
|
return self.active_lobbies[lobby_id].host_client_id
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region communication
|
|
|
|
|
|
|
|
|
|
# Sends a message to a single client in a given lobby.
|
2025-07-06 19:49:08 +02:00
|
|
|
async def send_message(self, lobby_id, client_id, message) -> None:
|
|
|
|
|
await self.active_lobbies[lobby_id].connected_clients[client_id].websocket.send(message)
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Sends a message to all clients in a given lobby.
|
2025-07-06 19:49:08 +02:00
|
|
|
async def broadcast_message(self, lobby_id, message) -> None:
|
|
|
|
|
connected_clients = self.active_lobbies[lobby_id].connected_clients
|
|
|
|
|
for client_id in connected_clients:
|
|
|
|
|
await connected_clients[client_id].websocket.send(message)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-07-07 22:40:46 +02:00
|
|
|
#region gameplay
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
async def spread_cards(self, lobby_id, round, cards, playing_order) -> None:
|
2025-07-07 22:40:46 +02:00
|
|
|
client_ids = self.get_lobby_client_ids(lobby_id)
|
|
|
|
|
|
|
|
|
|
trump = None
|
|
|
|
|
if cards["trump"]:
|
|
|
|
|
trump = cards["trump"]
|
2025-07-15 11:31:01 +02:00
|
|
|
cards.pop("trump")
|
2025-07-07 22:40:46 +02:00
|
|
|
|
|
|
|
|
for client_id in client_ids:
|
|
|
|
|
await self.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "new_round_started",
|
2025-07-08 10:42:34 +02:00
|
|
|
"current_round": round,
|
2025-07-15 11:31:01 +02:00
|
|
|
"cards": cards,
|
2025-07-07 22:40:46 +02:00
|
|
|
"trump": trump,
|
2025-07-08 10:42:34 +02:00
|
|
|
"playing_order": playing_order,
|
2025-07-07 22:40:46 +02:00
|
|
|
})
|
|
|
|
|
)
|
2025-07-08 10:42:34 +02:00
|
|
|
|
2025-07-15 11:51:02 +02:00
|
|
|
async def request_bet(self, lobby_id, client_id, disallowed_bet) -> None:
|
|
|
|
|
await self.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "request_bet",
|
|
|
|
|
"disallowed_bet": disallowed_bet,
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
async def spread_bet(self, lobby_id, client_id, bet) -> None:
|
|
|
|
|
await self.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
2025-07-09 16:48:45 +02:00
|
|
|
"response": "client_submitted_bet",
|
2025-07-08 10:42:34 +02:00
|
|
|
"client_id": client_id,
|
|
|
|
|
"bet": bet,
|
2025-07-15 12:11:50 +02:00
|
|
|
}),
|
2025-07-08 10:42:34 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def spread_played_card(self, lobby_id, client_id, played_card) -> None:
|
|
|
|
|
await self.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "client_played_card",
|
|
|
|
|
"client_id": client_id,
|
2025-07-15 14:42:06 +02:00
|
|
|
"played_card": played_card,
|
2025-07-15 12:11:50 +02:00
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def spread_play_winner(self, lobby_id, client_id) -> None:
|
|
|
|
|
await self.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "client_won_play",
|
|
|
|
|
"client_id": client_id,
|
|
|
|
|
}),
|
2025-07-08 10:42:34 +02:00
|
|
|
)
|
2025-07-07 22:40:46 +02:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
#region miscellaneous
|
|
|
|
|
|
|
|
|
|
# Generates an ID and ensures uniqueness among all lobbies.
|
|
|
|
|
def get_unique_lobby_id(self) -> str:
|
|
|
|
|
id = generate_code()
|
|
|
|
|
if self.lobby_id_exists(id):
|
|
|
|
|
return self.get_unique_lobby_id()
|
|
|
|
|
return id
|
|
|
|
|
|
|
|
|
|
# Generates an ID and ensures uniqueness among all clients in a given lobby.
|
|
|
|
|
def get_unique_client_id(self, lobby_id) -> str:
|
|
|
|
|
id = generate_code()
|
|
|
|
|
if self.client_id_exists_in_lobby(lobby_id, id):
|
|
|
|
|
return self.get_unique_client_id(lobby_id)
|
|
|
|
|
return id
|
|
|
|
|
|
|
|
|
|
#endregion
|
2025-07-06 19:49:08 +02:00
|
|
|
|
|
|
|
|
lobby_manager = LobbyManager()
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Listen for incoming connections
|
2025-07-06 19:49:08 +02:00
|
|
|
async def listen_websocket(run_local: bool) -> None:
|
|
|
|
|
ssl_context = None
|
|
|
|
|
if not run_local:
|
|
|
|
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
|
|
|
ssl_context.load_cert_chain("/etc/letsencrypt/live/denizk0461.dev/fullchain.pem", "/etc/letsencrypt/live/denizk0461.dev/privkey.pem")
|
|
|
|
|
async with serve(start_listen_client_websocket, "127.0.0.1", 9974, ssl=ssl_context) as server:
|
|
|
|
|
await server.serve_forever()
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Connect to a websocket and listen to their messages
|
2025-07-06 19:49:08 +02:00
|
|
|
async def start_listen_client_websocket(websocket) -> None:
|
|
|
|
|
async for message in websocket:
|
|
|
|
|
# await
|
|
|
|
|
await process_input(message, websocket)
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Retrieves and processes an input from a connected websocket
|
2025-07-06 19:49:08 +02:00
|
|
|
async def process_input(message: str, websocket) -> None:
|
|
|
|
|
data_object = json.loads(message)
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Create a new lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
if data_object["response"] == "create_lobby":
|
|
|
|
|
new_lobby_id = lobby_manager.get_unique_lobby_id()
|
|
|
|
|
new_client_id = lobby_manager.get_unique_client_id(new_lobby_id)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
# Create lobby with requesting client as host
|
2025-07-06 19:49:08 +02:00
|
|
|
lobby_manager.create_lobby(
|
|
|
|
|
new_lobby_id,
|
|
|
|
|
new_client_id,
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
2025-07-07 10:57:39 +02:00
|
|
|
# Join requesting client to new lobby.
|
|
|
|
|
# No error handling here, since lobby was just created, so it
|
|
|
|
|
# must exist and be empty
|
2025-07-06 19:49:08 +02:00
|
|
|
await lobby_manager.join_lobby(
|
|
|
|
|
new_lobby_id,
|
|
|
|
|
new_client_id,
|
|
|
|
|
data_object["client_name"],
|
|
|
|
|
websocket,
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
# Send success confirmation
|
2025-07-06 19:49:08 +02:00
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
new_lobby_id,
|
|
|
|
|
new_client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "create_lobby",
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"lobby_id": new_lobby_id,
|
|
|
|
|
"client_id": new_client_id,
|
|
|
|
|
}),
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
print(f"Client {new_client_id} created lobby {new_lobby_id}")
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Join client to existing lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
elif data_object["response"] == "join_lobby":
|
|
|
|
|
lobby_id = data_object["lobby_id"]
|
2025-07-07 10:57:39 +02:00
|
|
|
client_id = lobby_manager.get_unique_client_id(lobby_id)
|
|
|
|
|
client_name = data_object["client_name"]
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Join client to lobby
|
2025-07-07 10:57:39 +02:00
|
|
|
response = await lobby_manager.join_lobby(
|
2025-07-06 19:49:08 +02:00
|
|
|
lobby_id,
|
2025-07-07 10:57:39 +02:00
|
|
|
client_id,
|
|
|
|
|
client_name,
|
2025-07-06 19:49:08 +02:00
|
|
|
websocket,
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
|
2025-07-07 10:57:39 +02:00
|
|
|
match response:
|
|
|
|
|
case "lobby_not_found":
|
|
|
|
|
await websocket.send(
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "join_lobby_response",
|
|
|
|
|
"status": "lobby_not_found",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print(f"Client {client_name} could not connect; lobby {lobby_id} does not exist")
|
|
|
|
|
|
|
|
|
|
case "lobby_full":
|
|
|
|
|
await websocket.send(
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "join_lobby_response",
|
|
|
|
|
"status": "lobby_full",
|
|
|
|
|
})
|
|
|
|
|
)
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:57:39 +02:00
|
|
|
print(f"Client {client_name} could not connect; lobby {lobby_id} is full")
|
|
|
|
|
|
|
|
|
|
case "ok":
|
|
|
|
|
# Send success confirmation
|
|
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "join_lobby_response",
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"client_id": client_id,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print(f"Client {client_id} joined lobby {lobby_id}")
|
2025-07-07 10:31:22 +02:00
|
|
|
|
|
|
|
|
# Disconnect client from lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
elif data_object["response"] == "leave_lobby":
|
|
|
|
|
lobby_id = data_object["lobby_id"]
|
2025-07-07 10:31:22 +02:00
|
|
|
client_id = data_object["client_id"]
|
2025-07-06 19:49:08 +02:00
|
|
|
host_client_id = lobby_manager.get_lobby_host_client_id(lobby_id)
|
|
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
if host_client_id == client_id:
|
|
|
|
|
# Client is host; close lobby
|
|
|
|
|
await lobby_manager.leave_lobby(lobby_id, client_id)
|
2025-07-06 19:49:08 +02:00
|
|
|
await lobby_manager.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "leave_lobby_request",
|
|
|
|
|
})
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
print(f"Client {client_id} was host; broadcasting leave request")
|
2025-07-06 19:49:08 +02:00
|
|
|
else:
|
2025-07-07 10:31:22 +02:00
|
|
|
# Client is not host; client leaves only
|
|
|
|
|
await lobby_manager.leave_lobby(lobby_id, client_id)
|
2025-07-07 09:58:34 +02:00
|
|
|
|
|
|
|
|
# Delete empty lobby
|
|
|
|
|
if lobby_manager.get_lobby_player_count(lobby_id) == 0:
|
|
|
|
|
lobby_manager.delete_lobby(lobby_id)
|
2025-07-07 10:31:22 +02:00
|
|
|
print(f"Lobby {lobby_id} was disbanded")
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-07 10:31:22 +02:00
|
|
|
# Relays a chat message to all clients connected to a lobby
|
2025-07-06 19:49:08 +02:00
|
|
|
elif data_object["response"] == "send_chat_message":
|
2025-07-07 10:31:22 +02:00
|
|
|
client_name = lobby_manager.get_lobby_client_name(data_object["lobby_id"], data_object["client_id"])
|
2025-07-06 19:49:08 +02:00
|
|
|
await lobby_manager.broadcast_message(
|
|
|
|
|
data_object["lobby_id"],
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "receive_chat_message",
|
|
|
|
|
"message": data_object["message"],
|
2025-07-07 10:31:22 +02:00
|
|
|
"client_name": client_name,
|
2025-07-06 19:49:08 +02:00
|
|
|
})
|
|
|
|
|
)
|
2025-07-07 10:31:22 +02:00
|
|
|
print(f"Relayed chat message by {client_name}")
|
2025-07-07 22:40:46 +02:00
|
|
|
|
|
|
|
|
# Start game (host only)
|
|
|
|
|
elif data_object["response"] == "start_game":
|
2025-07-08 10:42:34 +02:00
|
|
|
lobby_id = data_object["lobby_id"]
|
|
|
|
|
lobby = lobby_manager.get_lobby(lobby_id)
|
|
|
|
|
lobby.client_playing_order = list(lobby.connected_clients.keys())
|
|
|
|
|
random.shuffle(lobby.client_playing_order)
|
2025-07-07 22:40:46 +02:00
|
|
|
|
|
|
|
|
await lobby_manager.spread_cards(
|
|
|
|
|
data_object["lobby_id"],
|
2025-07-08 10:42:34 +02:00
|
|
|
1,
|
2025-07-07 22:40:46 +02:00
|
|
|
data_object["cards"],
|
2025-07-08 10:42:34 +02:00
|
|
|
lobby.client_playing_order,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print(f"Lobby {lobby_id} is starting the game, playing order is: {lobby.client_playing_order}")
|
|
|
|
|
|
2025-07-15 11:51:02 +02:00
|
|
|
await lobby_manager.request_bet(
|
|
|
|
|
lobby_id,
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_playing_order[0],
|
|
|
|
|
-1,
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-15 19:38:27 +02:00
|
|
|
elif data_object["response"] == "start_next_play":
|
|
|
|
|
lobby_id = data_object["lobby_id"]
|
|
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_playing_order[0],
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "request_card",
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
# Start next round (host only)
|
|
|
|
|
elif data_object["response"] == "start_next_round":
|
|
|
|
|
lobby_id = data_object["lobby_id"]
|
|
|
|
|
|
2025-07-09 16:48:45 +02:00
|
|
|
# Reset bets
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_bets = {}
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
await lobby_manager.spread_cards(
|
|
|
|
|
lobby_id,
|
|
|
|
|
data_object["round"],
|
|
|
|
|
data_object["cards"],
|
|
|
|
|
None,
|
2025-07-09 16:48:45 +02:00
|
|
|
)
|
2025-07-08 10:42:34 +02:00
|
|
|
|
2025-07-15 11:51:02 +02:00
|
|
|
await lobby_manager.request_bet(
|
|
|
|
|
lobby_id,
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_playing_order[0],
|
|
|
|
|
-1,
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
elif data_object["response"] == "submit_bet":
|
2025-07-09 16:48:45 +02:00
|
|
|
lobby_id = data_object["lobby_id"]
|
|
|
|
|
client_id = data_object["client_id"]
|
|
|
|
|
bet = data_object["bet"]
|
|
|
|
|
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_bets[client_id] = bet
|
2025-07-08 10:42:34 +02:00
|
|
|
|
|
|
|
|
await lobby_manager.spread_bet(
|
2025-07-09 16:48:45 +02:00
|
|
|
lobby_id,
|
|
|
|
|
client_id,
|
|
|
|
|
bet,
|
2025-07-08 10:42:34 +02:00
|
|
|
)
|
|
|
|
|
|
2025-07-09 16:48:45 +02:00
|
|
|
last_client_index = lobby_manager.get_lobby(lobby_id).client_playing_order.index(client_id)
|
|
|
|
|
player_count = lobby_manager.get_lobby_player_count(lobby_id)
|
|
|
|
|
|
|
|
|
|
if last_client_index == (player_count - 1):
|
|
|
|
|
# all have bet; start game
|
|
|
|
|
await lobby_manager.broadcast_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "begin_playing_cards",
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-15 14:42:06 +02:00
|
|
|
print("Sent game start")
|
|
|
|
|
|
2025-07-09 16:48:45 +02:00
|
|
|
playing_client_id = lobby_manager.get_lobby(lobby_id).client_playing_order[0]
|
|
|
|
|
|
|
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
playing_client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "request_card",
|
|
|
|
|
}),
|
|
|
|
|
)
|
2025-07-15 14:42:06 +02:00
|
|
|
|
|
|
|
|
print(f"Playing client is {playing_client_id}")
|
2025-07-09 16:48:45 +02:00
|
|
|
else:
|
|
|
|
|
# bets are not finished; continue asking for bets
|
|
|
|
|
disallowed_bet = -1
|
|
|
|
|
if last_client_index == (player_count - 2):
|
|
|
|
|
# last client to bet; limit bet options
|
|
|
|
|
total_bet = sum(lobby_manager.get_lobby(lobby_id).client_bets.values())
|
|
|
|
|
disallowed_bet = total_bet - data_object["round"]
|
|
|
|
|
|
2025-07-15 11:51:02 +02:00
|
|
|
await lobby_manager.request_bet(
|
2025-07-09 16:48:45 +02:00
|
|
|
lobby_id,
|
|
|
|
|
lobby_manager.get_lobby(lobby_id).client_playing_order[last_client_index + 1],
|
2025-07-15 11:51:02 +02:00
|
|
|
disallowed_bet,
|
2025-07-09 16:48:45 +02:00
|
|
|
)
|
|
|
|
|
|
2025-07-08 10:42:34 +02:00
|
|
|
elif data_object["response"] == "play_card":
|
2025-07-09 16:48:45 +02:00
|
|
|
lobby_id = data_object["lobby_id"]
|
|
|
|
|
client_id = data_object["client_id"]
|
2025-07-08 10:42:34 +02:00
|
|
|
|
|
|
|
|
await lobby_manager.spread_played_card(
|
2025-07-09 16:48:45 +02:00
|
|
|
lobby_id,
|
|
|
|
|
client_id,
|
2025-07-08 10:42:34 +02:00
|
|
|
data_object["played_card"],
|
2025-07-07 22:40:46 +02:00
|
|
|
)
|
2025-07-15 12:11:50 +02:00
|
|
|
playing_order = lobby_manager.get_lobby(lobby_id).client_playing_order
|
|
|
|
|
client_index = playing_order.index(client_id)
|
|
|
|
|
if client_index == (len(playing_order) - 1):
|
|
|
|
|
# end round
|
|
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
lobby_manager.get_lobby_host_client_id(lobby_id),
|
|
|
|
|
json.dumps({
|
2025-07-15 14:42:06 +02:00
|
|
|
"response": "determine_play_winner_request",
|
2025-07-15 12:11:50 +02:00
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# not all clients have played a card yet; continue
|
|
|
|
|
playing_client_id = playing_order[client_index + 1]
|
2025-07-09 16:48:45 +02:00
|
|
|
|
|
|
|
|
await lobby_manager.send_message(
|
|
|
|
|
lobby_id,
|
|
|
|
|
playing_client_id,
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "request_card",
|
|
|
|
|
}),
|
|
|
|
|
)
|
2025-07-15 12:11:50 +02:00
|
|
|
|
|
|
|
|
elif data_object["response"] == "submit_play_winner":
|
|
|
|
|
await lobby_manager.spread_play_winner(
|
|
|
|
|
data_object["lobby_id"],
|
|
|
|
|
data_object["client_id"],
|
|
|
|
|
)
|
2025-07-06 19:49:08 +02:00
|
|
|
|
2025-07-15 14:42:06 +02:00
|
|
|
print(f"DING DING DING we have a winner!!! {data_object['client_id']}")
|
2025-07-16 11:15:05 +02:00
|
|
|
|
|
|
|
|
elif data_object["response"] == "end_game":
|
|
|
|
|
await lobby_manager.broadcast_message(
|
|
|
|
|
data_object["lobby_id"],
|
|
|
|
|
json.dumps({
|
|
|
|
|
"response": "end_game_request",
|
|
|
|
|
})
|
|
|
|
|
)
|
2025-07-15 14:42:06 +02:00
|
|
|
|
2025-07-06 19:49:08 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
|
run_local = False
|
2025-07-07 10:31:22 +02:00
|
|
|
# Run locally without SSL certificate
|
2025-07-06 19:49:08 +02:00
|
|
|
if len(sys.argv) > 1 and sys.argv[1] == "local":
|
|
|
|
|
run_local = True
|
|
|
|
|
|
|
|
|
|
print('Listening on *:9974')
|
|
|
|
|
asyncio.run(listen_websocket(run_local))
|