This commit is contained in:
Eden Kirin
2023-03-26 23:59:15 +02:00
parent f74bc9b52e
commit fa2aee881d
6 changed files with 31 additions and 13 deletions

View File

@ -53,7 +53,9 @@ async def get_game_info(
) )
@router.post("/game", response_model=StartGameResponseDto) @router.post(
"/game", response_model=StartGameResponseDto, status_code=status.HTTP_201_CREATED
)
async def start_game( async def start_game(
body: StartGameRequestDto, body: StartGameRequestDto,
engine: GameEngine = Depends(get_game_engine), engine: GameEngine = Depends(get_game_engine),
@ -72,7 +74,6 @@ async def start_game(
@router.get( @router.get(
"/player/{uuid}", "/player/{uuid}",
response_model=PlayerInfoResponseDto, response_model=PlayerInfoResponseDto,
status_code=status.HTTP_201_CREATED,
responses={ responses={
status.HTTP_403_FORBIDDEN: { status.HTTP_403_FORBIDDEN: {
"model": ErrorResponseDto, "model": ErrorResponseDto,

View File

@ -5,6 +5,7 @@ from typing import Optional
from hopper.enums import Direction, PlayerMoveResult from hopper.enums import Direction, PlayerMoveResult
from hopper.errors import Collision, PositionOutOfBounds from hopper.errors import Collision, PositionOutOfBounds
from hopper.interfaces import SendGameStateInterface
from hopper.models.board import ( from hopper.models.board import (
BOARD_DUMP_CHARS, BOARD_DUMP_CHARS,
BoardLayout, BoardLayout,
@ -17,12 +18,13 @@ from hopper.models.board import (
) )
from hopper.models.player import Player, PlayerList, Position from hopper.models.player import Player, PlayerList, Position
from hopper.watchdog import InactivityWatchdog from hopper.watchdog import InactivityWatchdog
from hopper.ws_server import WSServer
from settings import settings from settings import settings
class GameEngine: class GameEngine:
def __init__(self, board: GameBoard, ws_server: Optional[WSServer] = None) -> None: def __init__(
self, board: GameBoard, ws_server: Optional[SendGameStateInterface] = None
) -> None:
self.board = board self.board = board
self.ws_server = ws_server self.ws_server = ws_server
self.players = PlayerList() self.players = PlayerList()
@ -163,7 +165,7 @@ class GameEngineFactory:
board_width: int, board_width: int,
board_height: int, board_height: int,
obstacle_count: int = 0, obstacle_count: int = 0,
ws_server: Optional[WSServer] = None, ws_server: Optional[SendGameStateInterface] = None,
) -> GameEngine: ) -> GameEngine:
board = GameBoard( board = GameBoard(
width=board_width, width=board_width,
@ -188,7 +190,9 @@ class GameEngineFactory:
return game return game
@staticmethod @staticmethod
def create_default(ws_server: Optional[WSServer] = None) -> GameEngine: def create_default(
ws_server: Optional[SendGameStateInterface] = None,
) -> GameEngine:
return GameEngineFactory.create( return GameEngineFactory.create(
board_width=settings.board.WIDTH, board_width=settings.board.WIDTH,
board_height=settings.board.HEIGHT, board_height=settings.board.HEIGHT,

6
hopper/interfaces.py Normal file
View File

@ -0,0 +1,6 @@
from typing import Protocol
class SendGameStateInterface(Protocol):
async def send_game_state(self) -> None:
...

View File

@ -5,14 +5,18 @@ import time
from threading import Thread from threading import Thread
from typing import Optional from typing import Optional
from hopper.interfaces import SendGameStateInterface
from hopper.models.player import PlayerList from hopper.models.player import PlayerList
from hopper.ws_server import WSServer
from settings import settings from settings import settings
class InactivityWatchdog(Thread): class InactivityWatchdog(Thread):
def __init__( def __init__(
self, players: PlayerList, ws_server: Optional[WSServer] = None, *args, **kwargs self,
players: PlayerList,
ws_server: Optional[SendGameStateInterface] = None,
*args,
**kwargs,
) -> None: ) -> None:
self.players = players self.players = players
self.ws_server = ws_server self.ws_server = ws_server

View File

@ -12,20 +12,20 @@ from settings import settings
class WSServer(Thread): class WSServer(Thread):
def __init__(self, *args, **kwargs) -> None:
self.connected_clients = set[WebSocketServerProtocol]()
super().__init__(*args, **kwargs)
async def handler(self, websocket: WebSocketServerProtocol) -> None: async def handler(self, websocket: WebSocketServerProtocol) -> None:
"""New handler instance spawns for each connected client"""
self.connected_clients.add(websocket) self.connected_clients.add(websocket)
logging.info(f"Add client: {websocket.id}") logging.info(f"Add client: {websocket.id}")
try: try:
# send initial game state to connected client
await self.send_game_state_to_client(websocket) await self.send_game_state_to_client(websocket)
# loop and do nothing while client is connected
connected = True connected = True
while connected: while connected:
try: try:
message = await websocket.recv() # we're expecting nothing from client, but read if client sends a message
await websocket.recv()
except ConnectionClosedOK: except ConnectionClosedOK:
connected = False connected = False
finally: finally:
@ -49,11 +49,13 @@ class WSServer(Thread):
async def send_game_state_to_client( async def send_game_state_to_client(
self, websocket: WebSocketServerProtocol self, websocket: WebSocketServerProtocol
) -> None: ) -> None:
"""Send game state to the client"""
message = self._create_game_state_message() message = self._create_game_state_message()
logging.debug(f"Sending game state to client: {websocket.id}") logging.debug(f"Sending game state to client: {websocket.id}")
await websocket.send(message) await websocket.send(message)
async def send_game_state(self) -> None: async def send_game_state(self) -> None:
"""Broadcast game state to all connected clients"""
if not self.connected_clients: if not self.connected_clients:
return return
@ -77,4 +79,5 @@ class WSServer(Thread):
await asyncio.Future() # run forever await asyncio.Future() # run forever
def run(self) -> None: def run(self) -> None:
self.connected_clients = set[WebSocketServerProtocol]()
asyncio.run(self.run_async()) asyncio.run(self.run_async())