Cleanups
This commit is contained in:
@ -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,
|
||||||
|
|||||||
@ -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
6
hopper/interfaces.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class SendGameStateInterface(Protocol):
|
||||||
|
async def send_game_state(self) -> None:
|
||||||
|
...
|
||||||
@ -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
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user