Send purchase state

This commit is contained in:
Eden Kirin
2023-03-30 21:09:11 +02:00
parent 6111d07f09
commit 059408242c
5 changed files with 107 additions and 20 deletions

View File

@ -123,6 +123,18 @@
renderPlayers(data.players); renderPlayers(data.players);
} }
function productPurchaseStart(products) {
console.log("productPurchaseStart:", products)
}
function productPurchased(product) {
console.log("productPurchased:", product)
}
function productPurchaseDone() {
console.log("productPurchaseDone")
}
function wsConnect() { function wsConnect() {
let ws = new WebSocket('ws://localhost:8011'); let ws = new WebSocket('ws://localhost:8011');
ws.onopen = () => { ws.onopen = () => {
@ -137,6 +149,15 @@
case "game_dump": case "game_dump":
renderGameDump(wsMessage.data); renderGameDump(wsMessage.data);
break; break;
case "product_purchase_start":
productPurchaseStart(wsMessage.data)
break;
case "product_purchased":
productPurchased(wsMessage.data)
break;
case "product_purchase_done":
productPurchaseDone()
break;
default: default:
console.error("Unknown message:", wsMessage) console.error("Unknown message:", wsMessage)
} }

View File

@ -144,20 +144,14 @@ class GameEngine:
if self._is_player_on_destination(player): if self._is_player_on_destination(player):
player.state = PlayerState.ON_DESTINATION player.state = PlayerState.ON_DESTINATION
self._player_on_destination(player) await self._player_on_destination(player)
logging.info(f"Player {player} reached destination!") return PlayerMoveResult.DESTINATION_REACHED
if self.ws_server: if self.ws_server:
await self.ws_server.send_game_dump() await self.ws_server.send_game_dump()
self.__debug_print_board() self.__debug_print_board()
if player.state == PlayerState.ON_DESTINATION:
self.game_state = GameState.LOCK_FOR_MOVEMENT
return PlayerMoveResult.DESTINATION_REACHED
await asyncio.sleep(settings.game.MOVE_DELAY) await asyncio.sleep(settings.game.MOVE_DELAY)
return PlayerMoveResult.OK return PlayerMoveResult.OK
def _is_player_on_destination(self, player: Player) -> bool: def _is_player_on_destination(self, player: Player) -> bool:
@ -171,8 +165,15 @@ class GameEngine:
def _colided_with_obstacle(self, position: Position) -> bool: def _colided_with_obstacle(self, position: Position) -> bool:
return self.board.get_object_at_position(position) is not None return self.board.get_object_at_position(position) is not None
def _player_on_destination(self, player: Player) -> None: async def _player_on_destination(self, player: Player) -> None:
logging.info(f"Player {player} reached destination!")
self.game_state = GameState.LOCK_FOR_MOVEMENT self.game_state = GameState.LOCK_FOR_MOVEMENT
await self.ws_server.send_game_dump()
self.__debug_print_board()
await self.ws_server.send_product_purchase_message(products=settings.products)
logging.info(f"Starting purchase countdown timer for {settings.purchase_timeout} seconds") logging.info(f"Starting purchase countdown timer for {settings.purchase_timeout} seconds")
self._purchase_countdown_timer = CountdownTimer( self._purchase_countdown_timer = CountdownTimer(
seconds=settings.purchase_timeout, seconds=settings.purchase_timeout,
@ -183,6 +184,9 @@ class GameEngine:
def _on_purchase_timeout(self) -> None: def _on_purchase_timeout(self) -> None:
logging.info("Ding ding! Purchase countdown timer timeout") logging.info("Ding ding! Purchase countdown timer timeout")
self._purchase_countdown_timer = None self._purchase_countdown_timer = None
asyncio.run(self.ws_server.send_product_purchase_done_message(product=None))
self.game_state = GameState.RUNNING self.game_state = GameState.RUNNING
def get_board_layout(self) -> BoardLayout: def get_board_layout(self) -> BoardLayout:

View File

@ -1,6 +1,16 @@
from typing import Protocol from typing import Iterable, Optional, Protocol
from hopper.models.product import Product
class SendGameDumpInterface(Protocol): class SendGameDumpInterface(Protocol):
async def send_game_dump(self) -> None: async def send_game_dump(self) -> None:
... ...
async def send_product_purchase_message(self, products: Iterable[Product]) -> None:
...
async def send_product_purchase_done_message(
self, product: Optional[Product] = None
) -> None:
...

View File

@ -1,13 +1,14 @@
from __future__ import annotations from __future__ import annotations
import json import json
from typing import TypeVar, Generic from typing import Optional, TypeVar
from pydantic import Field from pydantic import Field
from pydantic.generics import GenericModel from pydantic.generics import GenericModel
from hopper.api.dto import BaseModel, BoardDto, DestinationDto, PlayerDto, PositionDto from hopper.api.dto import BaseModel, BoardDto, DestinationDto, PlayerDto, PositionDto
from hopper.enums import ObjectType from hopper.enums import ObjectType
from hopper.models.product import Product
class LayerObjectDto(BaseModel): class LayerObjectDto(BaseModel):
@ -20,6 +21,11 @@ class LayerDto(BaseModel):
objects: list[LayerObjectDto] objects: list[LayerObjectDto]
class ProductDto(BaseModel):
name: str
uuid: str
class GameDumpPlayerDto(PlayerDto): class GameDumpPlayerDto(PlayerDto):
... ...
@ -31,12 +37,20 @@ class GameDumpDto(BaseModel):
layers: list[LayerDto] layers: list[LayerDto]
class ProductPurchaseStartDto(BaseModel):
products: list[ProductDto]
class ProductPurchaseDoneDto(BaseModel):
product: Optional[ProductDto] = None
TMessageData = TypeVar("TMessageData", bound=BaseModel) TMessageData = TypeVar("TMessageData", bound=BaseModel)
class WSMessage(GenericModel): class WSMessage(GenericModel):
message: str message: str
data: TMessageData data: Optional[TMessageData] = None
def __str__(self) -> str: def __str__(self) -> str:
return self.to_str() return self.to_str()
@ -48,3 +62,13 @@ class WSMessage(GenericModel):
class WSGameDumpMessage(WSMessage): class WSGameDumpMessage(WSMessage):
message: str = "game_dump" message: str = "game_dump"
data: GameDumpDto data: GameDumpDto
class WSProductPurchaseStart(WSMessage):
message: str = "product_purchase_start"
data: ProductPurchaseStartDto
class WSProductPurchaseDone(WSMessage):
message: str = "product_purchase_done"
data: ProductPurchaseDoneDto

View File

@ -1,12 +1,22 @@
import asyncio import asyncio
import logging import logging
from threading import Thread from threading import Thread
from typing import Iterable, Optional
import websockets import websockets
from websockets import WebSocketServerProtocol from websockets import WebSocketServerProtocol
from websockets.exceptions import ConnectionClosedOK from websockets.exceptions import ConnectionClosedOK
from hopper.models.ws_dto import GameDumpDto, WSGameDumpMessage from hopper.models.product import Product
from hopper.models.ws_dto import (
GameDumpDto,
ProductPurchaseDoneDto,
ProductPurchaseStartDto,
WSGameDumpMessage,
WSMessage,
WSProductPurchaseDone,
WSProductPurchaseStart,
)
from settings import settings from settings import settings
@ -31,6 +41,19 @@ class WSServer(Thread):
self.connected_clients.remove(websocket) self.connected_clients.remove(websocket)
logging.info(f"Remove client: {websocket.id}") logging.info(f"Remove client: {websocket.id}")
async def send_message_to_client(
self, client: WebSocketServerProtocol, message: WSMessage
) -> None:
message_str = message.to_str()
logging.debug(
f"Sending message {message.message} to clients: {self.connected_clients}: {message_str}"
)
await client.send(message_str)
async def send_message_to_clients(self, message: WSMessage) -> None:
for client in self.connected_clients:
await self.send_message_to_client(client, message)
def _create_game_dump_message(self) -> WSGameDumpMessage: def _create_game_dump_message(self) -> WSGameDumpMessage:
# avoid circular imports # avoid circular imports
from hopper.api.dependencies import get_game_engine from hopper.api.dependencies import get_game_engine
@ -55,15 +78,20 @@ class WSServer(Thread):
async def send_game_dump(self) -> None: async def send_game_dump(self) -> None:
"""Broadcast game state to all connected clients""" """Broadcast game state to all connected clients"""
if not self.connected_clients:
return
message = self._create_game_dump_message() message = self._create_game_dump_message()
logging.debug( await self.send_message_to_clients(message)
f"Sending game dump to clients: {self.connected_clients}: {message}"
async def send_product_purchase_message(self, products: Iterable[Product]) -> None:
message = WSProductPurchaseStart(
data=ProductPurchaseStartDto(products=products)
) )
for client in self.connected_clients: await self.send_message_to_clients(message)
await client.send(message)
async def send_product_purchase_done_message(
self, product: Optional[Product] = None
) -> None:
message = WSProductPurchaseDone(data=ProductPurchaseDoneDto(product=product))
await self.send_message_to_clients(message)
async def run_async(self) -> None: async def run_async(self) -> None:
logging.info( logging.info(