Drop old purchase views and models

This commit is contained in:
Eden Kirin
2023-05-10 15:49:08 +02:00
parent 24d05dc234
commit 69e087c0c9
8 changed files with 79 additions and 127 deletions

View File

@ -8,6 +8,7 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
<script src="js/config.js"></script> <script src="js/config.js"></script>
<script src="js/frontend.js"></script> <script src="js/frontend.js"></script>
@ -19,6 +20,9 @@
<h1 class="mt-1 mb-2"> <h1 class="mt-1 mb-2">
FairHopper Visualisation Client FairHopper Visualisation Client
</h1> </h1>
<button type="button" class="btn btn-primary test-button">
Launch demo modal
</button>
<div id="purchase-container" class="purchase-container d-none"> <div id="purchase-container" class="purchase-container d-none">
<div class="d-flex header"> <div class="d-flex header">
<h3> <h3>
@ -42,6 +46,38 @@
</div> </div>
</div> </div>
</main> </main>
<div class="modal fade" id="player-on-destination-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Player on destination</h5>
</div>
<div class="modal-body">
Player <strong class="player-name"></strong>
reached destination in <strong class="move-count"></strong>
moves.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">
Finish product selection
</button>
</div>
</div>
</div>
</div>
<script>
document.querySelector(".test-button").onclick = () => {
playerReachedDestination({
player: {
name: "Pero Perić",
move_count: 123,
}
});
}
</script>
</body> </body>
</html> </html>

View File

@ -96,6 +96,17 @@ function renderGameDump(data) {
renderPlayers(data.players); renderPlayers(data.players);
} }
function playerReachedDestination(data) {
console.log(data);
const dlgElement = document.getElementById("player-on-destination-modal");
dlgElement.querySelector(".player-name").textContent = data.player.name;
dlgElement.querySelector(".move-count").textContent = data.player.move_count;
const modal = new bootstrap.Modal(dlgElement);
modal.show();
}
function productPurchaseStart(products, purchaseTimeout) { function productPurchaseStart(products, purchaseTimeout) {
console.log("productPurchaseStart:", products); console.log("productPurchaseStart:", products);
const containerElement = document.getElementById("purchase-container"); const containerElement = document.getElementById("purchase-container");
@ -149,6 +160,9 @@ function wsConnect() {
case "game_dump": case "game_dump":
renderGameDump(wsMessage.data); renderGameDump(wsMessage.data);
break; break;
case "player_on_destination":
playerReachedDestination(wsMessage.data);
break;
case "product_purchase_start": case "product_purchase_start":
productPurchaseStart(wsMessage.data.products, wsMessage.data.timeout); productPurchaseStart(wsMessage.data.products, wsMessage.data.timeout);
break; break;

View File

@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from typing import Optional
from pydantic import BaseModel as PydanticBaseModel from pydantic import BaseModel as PydanticBaseModel
from hopper.enums import PlayerState from hopper.enums import PlayerState
@ -40,11 +38,6 @@ class DestinationDto(BaseModel):
position: PositionDto position: PositionDto
class ProductDto(BaseModel):
name: str
id: str
description: Optional[str] = None
class StartGameRequestDto(BaseModel): class StartGameRequestDto(BaseModel):
player_name: str player_name: str
@ -68,11 +61,3 @@ class PlayerInfoResponseDto(MovePlayerResponseDto):
class ErrorResponseDto(BaseModel): class ErrorResponseDto(BaseModel):
detail: str detail: str
class GetProductsResponse(BaseModel):
products: list[ProductDto]
class PurchaseProductDto(BaseModel):
product_id: str

View File

@ -6,12 +6,9 @@ from hopper.api.dto import (
DestinationDto, DestinationDto,
ErrorResponseDto, ErrorResponseDto,
GameInfoDto, GameInfoDto,
GetProductsResponse,
MovePlayerResponseDto, MovePlayerResponseDto,
PingResponse, PingResponse,
PlayerInfoResponseDto, PlayerInfoResponseDto,
ProductDto,
PurchaseProductDto,
StartGameRequestDto, StartGameRequestDto,
StartGameResponseDto, StartGameResponseDto,
) )
@ -21,10 +18,8 @@ from hopper.errors import (
Collision, Collision,
GameLockForMovement, GameLockForMovement,
PositionOutOfBounds, PositionOutOfBounds,
PurchaseForbiddenForPlayer,
) )
from hopper.models.player import Player from hopper.models.player import Player
from settings import settings
router = APIRouter() router = APIRouter()
@ -153,58 +148,3 @@ async def move_player(
response.status_code = status.HTTP_200_OK response.status_code = status.HTTP_200_OK
return MovePlayerResponseDto(player=player) return MovePlayerResponseDto(player=player)
@router.get("/products", response_model=GetProductsResponse)
async def get_products() -> GetProductsResponse:
return GetProductsResponse(
products=settings.products,
)
@router.get("/products/{id}", response_model=ProductDto)
async def get_product(id: str) -> ProductDto:
for product in settings.products:
if product.id == id:
return ProductDto.from_orm(product)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Product not found"
)
@router.post(
"/player/{id}/product/purchase",
response_model=ProductDto,
responses={
status.HTTP_200_OK: {
"model": ProductDto,
"description": "Product purchased",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponseDto,
"description": "Purchase forbidden for this player",
},
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponseDto,
"description": " Player with id not found, probably kicked out",
},
},
)
async def purchase_product(
body: PurchaseProductDto,
engine: GameEngine = Depends(get_game_engine),
player: Player = Depends(get_player),
) -> ProductDto:
for product in settings.products:
if product.id == body.product_id:
try:
await engine.purchase_product(player=player, product=product)
return ProductDto.from_orm(product)
except PurchaseForbiddenForPlayer:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Purchase forbidden for this player",
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Product not found"
)

View File

@ -9,7 +9,6 @@ from hopper.errors import (
Collision, Collision,
GameLockForMovement, GameLockForMovement,
PositionOutOfBounds, PositionOutOfBounds,
PurchaseForbiddenForPlayer,
) )
from hopper.models.board import ( from hopper.models.board import (
BOARD_DUMP_CHARS, BOARD_DUMP_CHARS,
@ -22,7 +21,6 @@ from hopper.models.board import (
create_random_position, create_random_position,
) )
from hopper.models.player import Player, PlayerList, Position from hopper.models.player import Player, PlayerList, Position
from hopper.models.product import Product
from hopper.watchdog import InactivityWatchdog from hopper.watchdog import InactivityWatchdog
from hopper.ws_server import WSServer from hopper.ws_server import WSServer
from settings import settings from settings import settings
@ -178,9 +176,7 @@ class GameEngine:
self.game_state = GameState.LOCK_FOR_MOVEMENT self.game_state = GameState.LOCK_FOR_MOVEMENT
await self.send_game_dump() await self.send_game_dump()
await self.ws_server.send_product_purchase_start_message( await self.ws_server.send_player_reached_destination_message(player=player)
player=player, products=settings.products
)
logging.info( logging.info(
f"Starting purchase countdown timer for {settings.purchase_timeout} seconds" f"Starting purchase countdown timer for {settings.purchase_timeout} seconds"
@ -214,16 +210,16 @@ class GameEngine:
await asyncio.sleep(settings.game.PURCHASE_START_DELAY) await asyncio.sleep(settings.game.PURCHASE_START_DELAY)
async def purchase_product(self, player: Player, product: Product) -> None: # async def purchase_product(self, player: Player, product: Product) -> None:
if not player.state == PlayerState.ON_DESTINATION: # if not player.state == PlayerState.ON_DESTINATION:
raise PurchaseForbiddenForPlayer() # raise PurchaseForbiddenForPlayer()
if self._purchase_countdown_timer: # if self._purchase_countdown_timer:
self._purchase_countdown_timer.stop() # self._purchase_countdown_timer.stop()
await self.ws_server.send_product_purchase_done_message( # await self.ws_server.send_product_purchase_done_message(
player=player, product=product # player=player, product=product
) # )
await asyncio.sleep(settings.game.PURCHASE_FINISHED_DELAY) # await asyncio.sleep(settings.game.PURCHASE_FINISHED_DELAY)
await self.reset_game() # await self.reset_game()
def _reset_player(self, player) -> None: def _reset_player(self, player) -> None:
# move player to start position # move player to start position

View File

@ -12,7 +12,3 @@ class Collision(BaseError):
class GameLockForMovement(BaseError): class GameLockForMovement(BaseError):
... ...
class PurchaseForbiddenForPlayer(BaseError):
...

View File

@ -12,7 +12,6 @@ from hopper.api.dto import (
DestinationDto, DestinationDto,
PlayerDto, PlayerDto,
PositionDto, PositionDto,
ProductDto,
) )
from hopper.enums import ObjectType from hopper.enums import ObjectType
@ -34,20 +33,13 @@ class GameDumpDto(BaseModel):
layers: list[LayerDto] layers: list[LayerDto]
class ProductPurchaseStartDto(BaseModel):
player: PlayerDto
products: list[ProductDto]
timeout: int
class ProductPurchaseTimerDto(BaseModel): class ProductPurchaseTimerDto(BaseModel):
time_left: int time_left: int
player: PlayerDto player: PlayerDto
class ProductPurchaseDoneDto(BaseModel): class PlayerReachedDestinationDto(BaseModel):
player: PlayerDto player: PlayerDto
product: Optional[ProductDto] = None
TMessageData = TypeVar("TMessageData", bound=BaseModel) TMessageData = TypeVar("TMessageData", bound=BaseModel)
@ -69,11 +61,6 @@ class WSGameDumpMessage(WSMessage):
data: GameDumpDto data: GameDumpDto
class WSProductPurchaseStartMessage(WSMessage):
message: str = "product_purchase_start"
data: ProductPurchaseStartDto
class WSProductPurchaseTimerTickMessage(WSMessage): class WSProductPurchaseTimerTickMessage(WSMessage):
message: str = "product_purchase_timer_tick" message: str = "product_purchase_timer_tick"
data: ProductPurchaseTimerDto data: ProductPurchaseTimerDto
@ -81,4 +68,8 @@ class WSProductPurchaseTimerTickMessage(WSMessage):
class WSProductPurchaseDoneMessage(WSMessage): class WSProductPurchaseDoneMessage(WSMessage):
message: str = "product_purchase_done" message: str = "product_purchase_done"
data: ProductPurchaseDoneDto
class WSPlayerReachedDestinationMessage(WSMessage):
message: str = "player_reached_destination"
data: PlayerReachedDestinationDto

View File

@ -5,22 +5,19 @@ from typing import Iterable, Optional
import websockets import websockets
from websockets import WebSocketServerProtocol from websockets import WebSocketServerProtocol
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
from hopper.models.player import Player from hopper.models.player import Player
from hopper.models.product import Product from hopper.models.product import Product
from hopper.models.ws_dto import ( from hopper.models.ws_dto import (
GameDumpDto, GameDumpDto,
ProductPurchaseDoneDto, PlayerReachedDestinationDto,
ProductPurchaseStartDto,
ProductPurchaseTimerDto, ProductPurchaseTimerDto,
WSGameDumpMessage, WSGameDumpMessage,
WSMessage, WSMessage,
WSProductPurchaseDoneMessage, WSPlayerReachedDestinationMessage,
WSProductPurchaseStartMessage,
WSProductPurchaseTimerTickMessage, WSProductPurchaseTimerTickMessage,
) )
from settings import settings
class WSServer(Thread): class WSServer(Thread):
@ -93,14 +90,10 @@ class WSServer(Thread):
message = self._create_game_dump_message() message = self._create_game_dump_message()
await self.send_message_to_clients(message) await self.send_message_to_clients(message)
async def send_product_purchase_start_message( async def send_player_reached_destination_message(self, player: Player) -> None:
self, player: Player, products: Iterable[Product] message = WSPlayerReachedDestinationMessage(
) -> None: data=PlayerReachedDestinationDto(
message = WSProductPurchaseStartMessage(
data=ProductPurchaseStartDto(
player=player, player=player,
products=products,
timeout=settings.purchase_timeout,
) )
) )
await self.send_message_to_clients(message) await self.send_message_to_clients(message)
@ -119,10 +112,11 @@ class WSServer(Thread):
async def send_product_purchase_done_message( async def send_product_purchase_done_message(
self, player: Player, product: Optional[Product] = None self, player: Player, product: Optional[Product] = None
) -> None: ) -> None:
message = WSProductPurchaseDoneMessage( # message = WSProductPurchaseDoneMessage(
data=ProductPurchaseDoneDto(player=player, product=product), # data=ProductPurchaseDoneDto(player=player, product=product),
) # )
await self.send_message_to_clients(message) # await self.send_message_to_clients(message)
...
async def run_async(self) -> None: async def run_async(self) -> None:
logging.info( logging.info(