WS DTO assign rework

This commit is contained in:
Eden Kirin
2023-03-25 15:27:15 +01:00
parent 9aabcf61f4
commit 0f0fe68890
11 changed files with 79 additions and 30 deletions

View File

@ -2,9 +2,6 @@ from __future__ import annotations
from pydantic import BaseModel as PydanticBaseModel from pydantic import BaseModel as PydanticBaseModel
from hopper.models.board import GameBoard
from hopper.models.player import Player, Position
class BaseModel(PydanticBaseModel): class BaseModel(PydanticBaseModel):
class Config: class Config:
@ -19,30 +16,19 @@ class BoardDto(BaseModel):
width: int width: int
height: int height: int
@staticmethod
def from_model(board: GameBoard) -> BoardDto:
return BoardDto.from_orm(board)
class PositionDto(BaseModel): class PositionDto(BaseModel):
x: int x: int
y: int y: int
@staticmethod
def from_model(position: Position) -> PositionDto:
return PositionDto.from_orm(position)
class PlayerDto(BaseModel): class PlayerDto(BaseModel):
uuid: str uuid: str
active: bool
position: PositionDto position: PositionDto
move_count: int move_count: int
move_attempt_count: int move_attempt_count: int
@staticmethod
def from_model(player: Player) -> PlayerDto:
return PlayerDto.from_orm(player)
class DestinationDto(BaseModel): class DestinationDto(BaseModel):
position: PositionDto position: PositionDto

View File

@ -18,12 +18,14 @@ from hopper.api.dto import (
from hopper.engine import GameEngine from hopper.engine import GameEngine
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.ws_client import send_game_state
router = APIRouter() router = APIRouter()
@router.get("/ping", response_model=PingResponse) @router.get("/ping", response_model=PingResponse)
async def ping() -> PingResponse: async def ping() -> PingResponse:
await send_game_state()
return PingResponse( return PingResponse(
message="Pong!", message="Pong!",
) )
@ -34,9 +36,9 @@ async def get_game_info(
engine: GameEngine = Depends(get_game_engine), engine: GameEngine = Depends(get_game_engine),
) -> GameInfoDto: ) -> GameInfoDto:
return GameInfoDto( return GameInfoDto(
board=BoardDto.from_model(engine.board), board=engine.board,
destination=DestinationDto( destination=DestinationDto(
position=PositionDto.from_model(engine.board.destination.position) position=engine.board.destination.position,
), ),
) )
@ -49,10 +51,10 @@ async def start_game(
new_player = engine.start_game(player_name=body.player_name) new_player = engine.start_game(player_name=body.player_name)
return StartGameResponseDto( return StartGameResponseDto(
board=BoardDto.from_model(engine.board), board=engine.board,
player=PlayerDto.from_model(new_player), player=new_player,
destination=DestinationDto( destination=DestinationDto(
position=PositionDto.from_model(engine.board.destination.position) position=engine.board.destination.position,
), ),
) )
@ -76,7 +78,7 @@ async def get_player_info(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail="Player kicked out due to inactivity", detail="Player kicked out due to inactivity",
) )
return PlayerInfoResponseDto(player=PlayerDto.from_model(player)) return PlayerInfoResponseDto(player=player)
@router.post( @router.post(
@ -129,4 +131,4 @@ async def move_player(
if move_result == PlayerMoveResult.DESTINATION_REACHED: if move_result == PlayerMoveResult.DESTINATION_REACHED:
response.status_code = status.HTTP_200_OK response.status_code = status.HTTP_200_OK
return MovePlayerResponseDto(player=PlayerDto.from_model(player)) return MovePlayerResponseDto(player=player)

View File

@ -3,6 +3,7 @@ GET http://localhost:8010/ping
# create new game # create new game
POST http://localhost:8010/game POST http://localhost:8010/game
Content-Type: application/json
{ {
"player_name": "Mirko" "player_name": "Mirko"

View File

@ -148,6 +148,7 @@ class GameEngineFactory:
name="Pero", name="Pero",
uuid="test-player-id", uuid="test-player-id",
position=Position(2, 2), position=Position(2, 2),
can_be_deactivated=False,
) )
players.append(player) players.append(player)
logging.info(f"Test player created: {player}") logging.info(f"Test player created: {player}")

View File

@ -9,10 +9,10 @@ class Direction(Enum):
class ObjectType(str, Enum): class ObjectType(str, Enum):
NONE = auto() NONE = "NONE"
OBSTACLE = auto() OBSTACLE = "OBSTACLE"
PLAYER = auto() PLAYER = "PLAYER"
DESTINATION = auto() DESTINATION = "DESTINATION"
class PlayerMoveResult(Enum): class PlayerMoveResult(Enum):

View File

@ -19,7 +19,7 @@ class InactivityWatchdogSettings:
@dataclass @dataclass
class WSServerSettings: class WSServerSettings:
HOST: str = "localhost" HOST: str = "localhost"
PORT: int = 8010 PORT: int = 8011
@dataclass @dataclass

View File

@ -21,6 +21,7 @@ class Player:
default_factory=lambda: datetime.datetime.now() default_factory=lambda: datetime.datetime.now()
) )
active: bool = True active: bool = True
can_be_deactivated: bool = True
def reset_timeout(self) -> None: def reset_timeout(self) -> None:
self.last_seen = datetime.datetime.now() self.last_seen = datetime.datetime.now()

23
hopper/models/ws_dto.py Normal file
View File

@ -0,0 +1,23 @@
from __future__ import annotations
from pydantic import Field
from hopper.api.dto import BaseModel, BoardDto, DestinationDto, PlayerDto, PositionDto
from hopper.enums import ObjectType
class LayerObjectDto(BaseModel):
type: ObjectType = Field(..., alias="type_")
position: PositionDto
class LayerDto(BaseModel):
name: str
objects: list[LayerObjectDto]
class GameStateDto(BaseModel):
board: BoardDto
destination: DestinationDto
players: list[PlayerDto]
layers: list[LayerDto]

View File

@ -29,7 +29,11 @@ class InactivityWatchdog(Thread):
) )
for player in self.players: for player in self.players:
if player.active and player.last_seen < inactivity_threshold: if (
player.can_be_deactivated
and player.active
and player.last_seen < inactivity_threshold
):
player.active = False player.active = False
logging.info(f"Player {player} set as inactive") logging.info(f"Player {player} set as inactive")
@ -37,7 +41,7 @@ class InactivityWatchdog(Thread):
n = 0 n = 0
while n < len(self.players): while n < len(self.players):
player = self.players[n] player = self.players[n]
if player.last_seen < kick_threshold: if player.can_be_deactivated and player.last_seen < kick_threshold:
self.players.pop(n) self.players.pop(n)
logging.info(f"Player {player} kicked out") logging.info(f"Player {player} kicked out")
else: else:

31
hopper/ws_client.py Normal file
View File

@ -0,0 +1,31 @@
import json
from contextlib import asynccontextmanager
import websockets
from hopper.api.dependencies import get_game_engine
from hopper.models.ws_dto import GameStateDto
from settings import settings
@asynccontextmanager
async def create_ws_client() -> websockets.WebSocketServerProtocol:
ws_uri = f"ws://{settings.ws_server.HOST}:{settings.ws_server.PORT}"
async with websockets.connect(uri=ws_uri) as websocket:
yield websocket
async def send_game_state() -> None:
async with create_ws_client() as websocket:
engine = get_game_engine()
game_state = GameStateDto(
board=engine.board,
destination=engine.board.destination,
players=engine.players,
layers=engine.board.layers,
)
print(json.dumps(game_state.dict(), indent=4))
await websocket.send(json.dumps(game_state.dict()))

View File

@ -22,8 +22,8 @@ async def ws_handler(websocket: WebSocketServerProtocol):
try: try:
async for message in websocket: async for message in websocket:
print(">>>>>>>>>>", message)
broadcast(connected_clients, message) broadcast(connected_clients, message)
# await websocket.send(f"Are you talking to me? {message}")
finally: finally:
connected_clients.remove(websocket) connected_clients.remove(websocket)
logging.info(f"Remove client: {websocket}") logging.info(f"Remove client: {websocket}")