Drop old purchase views and models
This commit is contained in:
@ -6,8 +6,9 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<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>
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
|
||||||
|
|||||||
@ -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"
|
|
||||||
)
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -12,7 +12,3 @@ class Collision(BaseError):
|
|||||||
|
|
||||||
class GameLockForMovement(BaseError):
|
class GameLockForMovement(BaseError):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class PurchaseForbiddenForPlayer(BaseError):
|
|
||||||
...
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user