diff --git a/README.md b/README.md index 59b493e..b2b8085 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ actor "Player 1" as P1 actor "Player 2" as P2 actor "Player 3" as P3 - package Masterpiece #seashell { rectangle "FairHopper Game Server" #lightcyan { usecase API as "API Server" diff --git a/hopper/engine.py b/hopper/engine.py index 01ea11f..8f158c1 100644 --- a/hopper/engine.py +++ b/hopper/engine.py @@ -5,7 +5,7 @@ from typing import Optional from hopper.enums import Direction, PlayerMoveResult from hopper.errors import Collision, PositionOutOfBounds -from hopper.interfaces import SendGameStateInterface +from hopper.interfaces import SendGameDumpInterface from hopper.models.board import ( BOARD_DUMP_CHARS, BoardLayout, @@ -23,7 +23,7 @@ from settings import settings class GameEngine: def __init__( - self, board: GameBoard, ws_server: Optional[SendGameStateInterface] = None + self, board: GameBoard, ws_server: Optional[SendGameDumpInterface] = None ) -> None: self.board = board self.ws_server = ws_server @@ -74,7 +74,7 @@ class GameEngine: self.__debug_print_board() if self.ws_server: - await self.ws_server.send_game_state() + await self.ws_server.send_game_dump() await asyncio.sleep(settings.game.MOVE_DELAY) return player @@ -136,7 +136,7 @@ class GameEngine: logging.info(f"Player {player} reached destination!") if self.ws_server: - await self.ws_server.send_game_state() + await self.ws_server.send_game_dump() self.__debug_print_board() @@ -168,7 +168,7 @@ class GameEngineFactory: board_width: int, board_height: int, obstacle_count: int = 0, - ws_server: Optional[SendGameStateInterface] = None, + ws_server: Optional[SendGameDumpInterface] = None, ) -> GameEngine: board = GameBoard( width=board_width, @@ -194,7 +194,7 @@ class GameEngineFactory: @staticmethod def create_default( - ws_server: Optional[SendGameStateInterface] = None, + ws_server: Optional[SendGameDumpInterface] = None, ) -> GameEngine: return GameEngineFactory.create( board_width=settings.board.WIDTH, diff --git a/hopper/enums.py b/hopper/enums.py index 0068bef..bd91853 100644 --- a/hopper/enums.py +++ b/hopper/enums.py @@ -18,3 +18,9 @@ class ObjectType(str, Enum): class PlayerMoveResult(Enum): OK = auto() DESTINATION_REACHED = auto() + + +class GameState(Enum): + RUNNING = auto() + LOCKED_FOR_PRODUCT_SELECTION = auto() + ENDGAME = auto() diff --git a/hopper/interfaces.py b/hopper/interfaces.py index 4a5a0e1..503515e 100644 --- a/hopper/interfaces.py +++ b/hopper/interfaces.py @@ -1,6 +1,6 @@ from typing import Protocol -class SendGameStateInterface(Protocol): - async def send_game_state(self) -> None: +class SendGameDumpInterface(Protocol): + async def send_game_dump(self) -> None: ... diff --git a/hopper/models/ws_dto.py b/hopper/models/ws_dto.py index d0f644f..c6fd72a 100644 --- a/hopper/models/ws_dto.py +++ b/hopper/models/ws_dto.py @@ -15,12 +15,12 @@ class LayerDto(BaseModel): name: str objects: list[LayerObjectDto] -class GameStatePlayerDto(PlayerDto): +class GameDumpPlayerDto(PlayerDto): reached_destination: bool -class GameStateDto(BaseModel): +class GameDumpDto(BaseModel): board: BoardDto destination: DestinationDto - players: list[GameStatePlayerDto] + players: list[GameDumpPlayerDto] layers: list[LayerDto] diff --git a/hopper/watchdog.py b/hopper/watchdog.py index 9bf59b8..bf5b809 100644 --- a/hopper/watchdog.py +++ b/hopper/watchdog.py @@ -5,7 +5,7 @@ import time from threading import Thread from typing import Optional -from hopper.interfaces import SendGameStateInterface +from hopper.interfaces import SendGameDumpInterface from hopper.models.player import PlayerList from settings import settings @@ -14,7 +14,7 @@ class InactivityWatchdog(Thread): def __init__( self, players: PlayerList, - ws_server: Optional[SendGameStateInterface] = None, + ws_server: Optional[SendGameDumpInterface] = None, *args, **kwargs, ) -> None: @@ -38,7 +38,7 @@ class InactivityWatchdog(Thread): seconds=settings.inacivity_watchdog.KICK_TIMEOUT ) - send_game_state = False + send_game_dump = False for player in self.players: if ( @@ -48,7 +48,7 @@ class InactivityWatchdog(Thread): ): player.active = False logging.info(f"Player {player} set as inactive") - send_game_state = True + send_game_dump = True # safe remove from list n = 0 @@ -57,18 +57,18 @@ class InactivityWatchdog(Thread): if player.can_be_deactivated and player.last_seen < kick_threshold: self.players.pop(n) logging.info(f"Player {player} kicked out") - send_game_state = True + send_game_dump = True else: n += 1 - if send_game_state: - self.send_game_state() + if send_game_dump: + self.send_game_dump() - def send_game_state(self): + def send_game_dump(self): if not self.ws_server: return - logging.info("Sending WS game state") - asyncio.run(self.ws_server.send_game_state()) + logging.info("Sending WS game dump") + asyncio.run(self.ws_server.send_game_dump()) def stop(self) -> None: self.stopped = True diff --git a/hopper/ws_server.py b/hopper/ws_server.py index 43ef659..7e0ca99 100644 --- a/hopper/ws_server.py +++ b/hopper/ws_server.py @@ -7,7 +7,7 @@ import websockets from websockets import WebSocketServerProtocol from websockets.exceptions import ConnectionClosedOK -from hopper.models.ws_dto import GameStateDto +from hopper.models.ws_dto import GameDumpDto from settings import settings @@ -18,8 +18,8 @@ class WSServer(Thread): logging.info(f"Add client: {websocket.id}") try: - # send initial game state to connected client - await self.send_game_state_to_client(websocket) + # send initial game dump to connected client + await self.send_game_dump_to_client(websocket) # loop and do nothing while client is connected connected = True while connected: @@ -32,36 +32,36 @@ class WSServer(Thread): self.connected_clients.remove(websocket) logging.info(f"Remove client: {websocket.id}") - def _create_game_state_message(self) -> str: + def _create_game_dump_message(self) -> str: # avoid circular imports from hopper.api.dependencies import get_game_engine engine = get_game_engine() - game_state = GameStateDto( + game_dump = GameDumpDto( board=engine.board, destination=engine.board.destination, players=engine.players, layers=engine.get_board_layout().layers, ) - return json.dumps(game_state.dict()) + return json.dumps(game_dump.dict()) - async def send_game_state_to_client( + async def send_game_dump_to_client( self, websocket: WebSocketServerProtocol ) -> None: - """Send game state to the client""" - message = self._create_game_state_message() - logging.debug(f"Sending game state to client: {websocket.id}") + """Send game dump to the client""" + message = self._create_game_dump_message() + logging.debug(f"Sending game dump to client: {websocket.id}") await websocket.send(message) - async def send_game_state(self) -> None: + async def send_game_dump(self) -> None: """Broadcast game state to all connected clients""" if not self.connected_clients: return - message = self._create_game_state_message() + message = self._create_game_dump_message() logging.debug( - f"Sending game state to clients: {self.connected_clients}: {message}" + f"Sending game dump to clients: {self.connected_clients}: {message}" ) for client in self.connected_clients: await client.send(message)