WS DTO assign rework
This commit is contained in:
@ -2,9 +2,6 @@ from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel as PydanticBaseModel
|
||||
|
||||
from hopper.models.board import GameBoard
|
||||
from hopper.models.player import Player, Position
|
||||
|
||||
|
||||
class BaseModel(PydanticBaseModel):
|
||||
class Config:
|
||||
@ -19,30 +16,19 @@ class BoardDto(BaseModel):
|
||||
width: int
|
||||
height: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(board: GameBoard) -> BoardDto:
|
||||
return BoardDto.from_orm(board)
|
||||
|
||||
|
||||
class PositionDto(BaseModel):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(position: Position) -> PositionDto:
|
||||
return PositionDto.from_orm(position)
|
||||
|
||||
|
||||
class PlayerDto(BaseModel):
|
||||
uuid: str
|
||||
active: bool
|
||||
position: PositionDto
|
||||
move_count: int
|
||||
move_attempt_count: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(player: Player) -> PlayerDto:
|
||||
return PlayerDto.from_orm(player)
|
||||
|
||||
|
||||
class DestinationDto(BaseModel):
|
||||
position: PositionDto
|
||||
|
||||
@ -18,12 +18,14 @@ from hopper.api.dto import (
|
||||
from hopper.engine import GameEngine
|
||||
from hopper.enums import Direction, PlayerMoveResult
|
||||
from hopper.errors import Collision, PositionOutOfBounds
|
||||
from hopper.ws_client import send_game_state
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/ping", response_model=PingResponse)
|
||||
async def ping() -> PingResponse:
|
||||
await send_game_state()
|
||||
return PingResponse(
|
||||
message="Pong!",
|
||||
)
|
||||
@ -34,9 +36,9 @@ async def get_game_info(
|
||||
engine: GameEngine = Depends(get_game_engine),
|
||||
) -> GameInfoDto:
|
||||
return GameInfoDto(
|
||||
board=BoardDto.from_model(engine.board),
|
||||
board=engine.board,
|
||||
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)
|
||||
|
||||
return StartGameResponseDto(
|
||||
board=BoardDto.from_model(engine.board),
|
||||
player=PlayerDto.from_model(new_player),
|
||||
board=engine.board,
|
||||
player=new_player,
|
||||
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,
|
||||
detail="Player kicked out due to inactivity",
|
||||
)
|
||||
return PlayerInfoResponseDto(player=PlayerDto.from_model(player))
|
||||
return PlayerInfoResponseDto(player=player)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -129,4 +131,4 @@ async def move_player(
|
||||
if move_result == PlayerMoveResult.DESTINATION_REACHED:
|
||||
response.status_code = status.HTTP_200_OK
|
||||
|
||||
return MovePlayerResponseDto(player=PlayerDto.from_model(player))
|
||||
return MovePlayerResponseDto(player=player)
|
||||
|
||||
@ -3,6 +3,7 @@ GET http://localhost:8010/ping
|
||||
|
||||
# create new game
|
||||
POST http://localhost:8010/game
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"player_name": "Mirko"
|
||||
|
||||
@ -148,6 +148,7 @@ class GameEngineFactory:
|
||||
name="Pero",
|
||||
uuid="test-player-id",
|
||||
position=Position(2, 2),
|
||||
can_be_deactivated=False,
|
||||
)
|
||||
players.append(player)
|
||||
logging.info(f"Test player created: {player}")
|
||||
|
||||
@ -9,10 +9,10 @@ class Direction(Enum):
|
||||
|
||||
|
||||
class ObjectType(str, Enum):
|
||||
NONE = auto()
|
||||
OBSTACLE = auto()
|
||||
PLAYER = auto()
|
||||
DESTINATION = auto()
|
||||
NONE = "NONE"
|
||||
OBSTACLE = "OBSTACLE"
|
||||
PLAYER = "PLAYER"
|
||||
DESTINATION = "DESTINATION"
|
||||
|
||||
|
||||
class PlayerMoveResult(Enum):
|
||||
|
||||
@ -19,7 +19,7 @@ class InactivityWatchdogSettings:
|
||||
@dataclass
|
||||
class WSServerSettings:
|
||||
HOST: str = "localhost"
|
||||
PORT: int = 8010
|
||||
PORT: int = 8011
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@ -21,6 +21,7 @@ class Player:
|
||||
default_factory=lambda: datetime.datetime.now()
|
||||
)
|
||||
active: bool = True
|
||||
can_be_deactivated: bool = True
|
||||
|
||||
def reset_timeout(self) -> None:
|
||||
self.last_seen = datetime.datetime.now()
|
||||
|
||||
23
hopper/models/ws_dto.py
Normal file
23
hopper/models/ws_dto.py
Normal 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]
|
||||
@ -29,7 +29,11 @@ class InactivityWatchdog(Thread):
|
||||
)
|
||||
|
||||
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
|
||||
logging.info(f"Player {player} set as inactive")
|
||||
|
||||
@ -37,7 +41,7 @@ class InactivityWatchdog(Thread):
|
||||
n = 0
|
||||
while n < len(self.players):
|
||||
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)
|
||||
logging.info(f"Player {player} kicked out")
|
||||
else:
|
||||
|
||||
31
hopper/ws_client.py
Normal file
31
hopper/ws_client.py
Normal 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()))
|
||||
@ -22,8 +22,8 @@ async def ws_handler(websocket: WebSocketServerProtocol):
|
||||
|
||||
try:
|
||||
async for message in websocket:
|
||||
print(">>>>>>>>>>", message)
|
||||
broadcast(connected_clients, message)
|
||||
# await websocket.send(f"Are you talking to me? {message}")
|
||||
finally:
|
||||
connected_clients.remove(websocket)
|
||||
logging.info(f"Remove client: {websocket}")
|
||||
|
||||
Reference in New Issue
Block a user