revisions / additions / GOD DAMN IT

This commit is contained in:
codey 2024-09-19 20:20:16 -04:00
parent 6265342393
commit a4d9fa797c
3 changed files with 242 additions and 34 deletions

View File

@ -6,21 +6,22 @@ class CAHClient:
platform: str, platform: str,
csid: str, csid: str,
connected_at: int, connected_at: int,
current_game: str | None): players: list):
self.resource: str = resource self.resource: str = resource
self.platform: str = platform self.platform: str = platform
self.csid: str = csid self.csid: str = csid
self.connected_at: int = connected_at self.connected_at: int = connected_at
self.current_game: str | None = current_game self.players: list = players
def __iter__(self): def __iter__(self):
return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__() return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__()
class CAHGame: class CAHGame:
def __init__(self, def __init__(self,
id: str, id: str,
rounds: int, rounds: int,
resources: list[dict],
players: list[dict], players: list[dict],
created_at: int, created_at: int,
state: int, state: int,
@ -29,6 +30,7 @@ class CAHGame:
): ):
self.id: str = id self.id: str = id
self.rounds: int = rounds self.rounds: int = rounds
self.resources: list[dict] = resources
self.players: list[dict] = players self.players: list[dict] = players
self.created_at: int = created_at self.created_at: int = created_at
self.state: int = state self.state: int = state
@ -37,4 +39,21 @@ class CAHGame:
def __iter__(self): def __iter__(self):
return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__() return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__()
class CAHPlayer:
def __init__(self,
id: str,
current_game: CAHGame,
platform: str,
related_resource: str,
joined_at: str,
handle: str):
self.id = id
self.current_game = current_game
self.platform = platform
self.related_resource = related_resource
self.joined_at = joined_at
self.handle = handle

View File

@ -29,7 +29,6 @@ class ConnectionManager:
'resource': _client.resource, 'resource': _client.resource,
'platform': _client.platform, 'platform': _client.platform,
'connected_at': _client.connected_at, 'connected_at': _client.connected_at,
'current_game': _client.current_game if _client.current_game else None,
}) })
await websocket.send_json({ await websocket.send_json({
@ -62,6 +61,8 @@ class ConnectionManager:
csid: str, csid: str,
handshakedClient: CAHClient): handshakedClient: CAHClient):
if websocket in self.active_connections:
self.active_connections.pop(websocket)
self.active_connections[websocket] = { self.active_connections[websocket] = {
'websocket': websocket, 'websocket': websocket,
'csid': csid, 'csid': csid,
@ -80,12 +81,26 @@ class ConnectionManager:
await self.send_client_and_game_lists(state, await self.send_client_and_game_lists(state,
websocket) websocket)
def disconnect(self, websocket: WebSocket, csid: str = None): async def disconnect(self, state, websocket: WebSocket, csid: str = None):
disconnected = self.get_connection_by_ws(websocket)
disconnected_client = disconnected.get('client')
disconnected_resource = disconnected_client.resource
await self.broadcast({
"event": "client_disconnected",
"ts": int(time.time()),
"data": {
"disconnected_resource": disconnected_resource,
}
})
self.active_connections.pop(websocket) self.active_connections.pop(websocket)
async def send(self, message: str, websocket: WebSocket): async def send(self, message: str, websocket: WebSocket):
await websocket.send_json(message) await websocket.send_json(message)
async def broadcast(self, message: str): async def broadcast(self, message: str):
for connection in self.active_connections: for connection in self.active_connections:
await connection.send_json(message) try:
await connection.send_json(message)
except:
continue

View File

@ -1,12 +1,14 @@
#!/usr/bin/env python3.12 #!/usr/bin/env python3.12
from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi_utils.tasks import repeat_every
import time import time
import uuid import uuid
import json import json
import asyncio
import traceback import traceback
import random import random
from cah.constructors import CAHClient, CAHGame from cah.constructors import CAHClient, CAHPlayer, CAHGame
from cah.websocket_conn import ConnectionManager from cah.websocket_conn import ConnectionManager
class CAH(FastAPI): class CAH(FastAPI):
@ -36,6 +38,78 @@ class CAH(FastAPI):
for endpoint, handler in self.endpoints.items(): for endpoint, handler in self.endpoints.items():
app.add_api_route(f"/{endpoint}/", handler, methods=["POST"]) app.add_api_route(f"/{endpoint}/", handler, methods=["POST"])
asyncio.get_event_loop().create_task(self.send_heartbeats())
async def send_heartbeats(self):
while True:
print("Heartbeat!")
await self.connection_manager.broadcast({
"event": "heartbeat",
"ts": int(time.time())
})
await asyncio.sleep(5)
async def remove_player(self, game: str, player: str):
try:
_game = None
for __game in self.games:
if __game.id == game:
_game = __game
print(f"Got game!!!\n{game}")
for _player in _game.players:
if _player.get('id') == player:
_game.players.pop(_player)
await self.connection_manager.broadcast({
'event': 'player_left',
'ts': int(time.time()),
'data': {
'player': _player
}
}) # Change to broadcast to current game members only
except:
print(traceback.format_exc())
return {
'err': True,
'errorText': 'Server error'
}
async def join_player(self, player: CAHPlayer, game: str):
joined_game = self.get_game_by_id(game)
if not joined_game:
return {
'err': True,
'errorText': 'Game not found',
'data': {
'requestedGame': game
}
}
if player.current_game == joined_game:
return {
'err': True,
'errorText': 'You are already here.',
}
joined_game.players.append(player.__dict__)
await self.connection_manager.broadcast({
'event': 'player_joined',
'ts': int(time.time()),
'data': {
'player': player
}
}) # Change to broadcast to current game members only
return joined_game
async def cah_handler(self, websocket: WebSocket): async def cah_handler(self, websocket: WebSocket):
"""/cah WebSocket""" """/cah WebSocket"""
@ -62,7 +136,54 @@ class CAH(FastAPI):
data) data)
case 'create_game': case 'create_game':
await self.create_game(websocket, await self.create_game(websocket,
data) data)
case 'join_game':
sender = self.connection_manager.get_connection_by_ws(websocket)
sender_client = sender.get('client')
handle = data.get('handle')
game = data.get('game')
player = CAHPlayer(id=str(uuid.uuid4()),
current_game=game,
platform=sender_client.platform,
related_resource=sender_client.resource,
joined_at=int(time.time()),
handle=handle)
joined = await self.join_player(player, game)
if type(joined) == dict and joined.get('err'):
await sender.get('websocket').send_json({
'event': 'join_game_response',
'ts': int(time.time()),
'err': True,
'errorText': joined.get('errorText'),
})
else:
await sender.get('websocket').send_json({
'event': 'join_game_response',
'ts': int(time.time()),
'success': True,
'data': {
'game': joined.__dict__,
}
})
case 'leave_game':
sender = self.connection_manager.get_connection_by_ws(websocket)
player_handle = data.get('handle')
game = data.get('game')
player = self.get_player_by_handle(game, player_handle)
left = await self.remove_player(game, player)
if type(left) == dict and left.get('err'):
await sender.get('websocket').send_json({
'event': 'leave_game_response',
'ts': int(time.time()),
'err': True,
'errorText': left.get('errorText'),
})
else:
await sender.get('websocket').send_json({
'event': 'leave_game_response',
'ts': int(time.time()),
'success': True,
})
case _: case _:
sender = self.connection_manager.get_connection_by_ws(websocket) sender = self.connection_manager.get_connection_by_ws(websocket)
await self.connection_manager.broadcast({ await self.connection_manager.broadcast({
@ -72,17 +193,46 @@ class CAH(FastAPI):
"data": data, "data": data,
}) })
except WebSocketDisconnect: except WebSocketDisconnect:
disconnected = self.connection_manager.get_connection_by_ws(websocket) await self.connection_manager.disconnect(self, websocket)
self.connection_manager.disconnect(websocket)
await self.connection_manager.broadcast({
"event": "client_disconnected", def get_game_by_id(self, _id: str):
"ts": int(time.time()), for game in self.games:
"data": { if game.id == _id:
"disconnected_resource": disconnected.get('client').resource, return game
} return
})
def get_player_by_id(self, game: str, player: str):
game = self.get_game_by_id(game)
if not game:
return {
'err': True,
'errorText': f'Cannot lookup player for unknown game {game}'
}
for _player in game.players:
if _player.id == player:
return player
return {
'err': True,
'errorText': f'Player w/ uuid {player} not found in {game}'
}
def get_player_by_handle(self, game: str, player: str):
game = self.get_game_by_id(game)
if not game:
return {
'err': True,
'errorText': f'Cannot lookup player for unknown game {game}'
}
for _player in game.players:
if _player.get('handle') == player:
return player
return {
'err': True,
'errorText': f'Player {player} not found in {game}'
}
def get_games(self): def get_games(self):
try: try:
_games: list = [] _games: list = []
@ -92,6 +242,7 @@ class CAH(FastAPI):
_games.append({ _games.append({
'id': game.id, 'id': game.id,
'rounds': game.rounds, 'rounds': game.rounds,
'resources': game.resources,
'players': game.players, 'players': game.players,
'created_at': game.created_at, 'created_at': game.created_at,
'state': game.state, 'state': game.state,
@ -126,7 +277,7 @@ class CAH(FastAPI):
platform=platform, platform=platform,
csid=csid, csid=csid,
connected_at=int(time.time()), connected_at=int(time.time()),
current_game=None, players=[],
) )
await self.connection_manager.handshake_complete(self, websocket, csid, client) await self.connection_manager.handshake_complete(self, websocket, csid, client)
@ -144,6 +295,12 @@ class CAH(FastAPI):
async def create_game(self, websocket, data: str): async def create_game(self, websocket, data: str):
data = data.get('data') data = data.get('data')
if not self.connection_manager.get_connection_by_ws(websocket).get('client'): # No client set, valid handshake not completed
return await websocket.send_json({
"event": "create_game_response",
"err": True,
"errorText": "Unauthorized",
})
if not data.get('rounds') or not str(data.get('rounds')).isnumeric(): if not data.get('rounds') or not str(data.get('rounds')).isnumeric():
return await websocket.send_json({ return await websocket.send_json({
"event": "create_game_response", "event": "create_game_response",
@ -154,24 +311,41 @@ class CAH(FastAPI):
"recvdData": data, "recvdData": data,
} }
}) })
client = self.connection_manager.get_connection_by_ws(websocket).get('client') if len(self.games):
rounds = int(data.get('rounds')) await websocket.send_json({
game_uuid = str(uuid.uuid4()) 'event': 'create_game_response',
game = CAHGame(id=game_uuid, 'ts': int(time.time()),
rounds=rounds, 'data': {
players=[vars(client),], 'err': True,
created_at=int(time.time()), 'errorText': 'A game already exists' # One game limit
state=-1, }
started_at=0, })
state_changed_at=int(time.time())) else:
self.games.append(game) client = self.connection_manager.get_connection_by_ws(websocket).get('client')
client.current_game = game.id rounds = int(data.get('rounds'))
await websocket.send_json({ game_uuid = str(uuid.uuid4())
game = CAHGame(id=game_uuid,
rounds=rounds,
resources=[client.resource,],
players=[],
created_at=int(time.time()),
state=-1,
started_at=0,
state_changed_at=int(time.time()))
await websocket.send_json({
"event": "create_game_response", "event": "create_game_response",
"ts": int(time.time()), "ts": int(time.time()),
"data": { "data": {
"success": True, "success": True,
"createdGame": client.current_game, "createdGame": game.__dict__,
} }
}) })
await self.connection_manager.send_client_and_game_lists(self, websocket) await self.connection_manager.broadcast({
'event': 'game_created',
'ts': int(time.time()),
'data': {
'game': game.__dict__,
}
})
self.games.append(game)
await self.connection_manager.send_client_and_game_lists(self, websocket)