Product purchase

This commit is contained in:
Eden Kirin
2023-03-31 13:06:27 +02:00
parent e1e77aba96
commit 28a981980f
7 changed files with 108 additions and 18 deletions

View File

@ -34,6 +34,15 @@ POST http://localhost:8010/player/test-player-pero/move/up
POST http://localhost:8010/player/test-player-pero/move/down
###
# purchase product
POST http://localhost:8010/player/test-player-pero/product/purchase
Content-Type: application/json
{
"product_uuid": "cocacola-id"
}
###
# move Mirko left
POST http://localhost:8010/player/test-player-mirko/move/left
###

View File

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

View File

@ -6,16 +6,20 @@ from hopper.api.dto import (
DestinationDto,
ErrorResponseDto,
GameInfoDto,
GetProductsResponse,
MovePlayerResponseDto,
PingResponse,
PlayerInfoResponseDto,
ProductDto,
PurchaseProductDto,
StartGameRequestDto,
StartGameResponseDto,
)
from hopper.engine import GameEngine
from hopper.enums import Direction, PlayerMoveResult
from hopper.errors import Collision, GameLockForMovement, PositionOutOfBounds
from hopper.errors import Collision, GameLockForMovement, PositionOutOfBounds, PurchaseForbiddenForPlayer
from hopper.models.player import Player
from settings import settings
router = APIRouter()
@ -144,3 +148,42 @@ async def move_player(
response.status_code = status.HTTP_200_OK
return MovePlayerResponseDto(player=player)
@router.get("/products", response_model=GetProductsResponse)
async def get_products() -> GetProductsResponse:
return GetProductsResponse(
products=settings.products,
)
@router.get("/products/{uuid}", response_model=ProductDto)
async def get_product(uuid: str) -> ProductDto:
for product in settings.products:
if product.uuid == uuid:
return ProductDto.from_orm(product)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Product not found"
)
@router.post("/player/{uuid}/product/purchase")
async def purchase_product(
body: PurchaseProductDto,
engine: GameEngine = Depends(get_game_engine),
player: Player = Depends(get_player),
):
for product in settings.products:
if product.uuid == body.product_uuid:
try:
await engine.purchase_product(player=player, product=product)
except PurchaseForbiddenForPlayer:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Purchase forbidden for this player",
)
break
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Product not found"
)

View File

@ -5,7 +5,10 @@ from typing import Callable, Optional
class CountdownTimer(Thread):
def __init__(
self, seconds: int, timer_tick_callback: Optional[Callable[[int], None]] = None, timer_done_callback: Optional[Callable[[], None]] = None
self,
seconds: int,
timer_tick_callback: Optional[Callable[[int], None]] = None,
timer_done_callback: Optional[Callable[[], None]] = None,
) -> None:
self.seconds = seconds
self.stop_event = Event()
@ -18,7 +21,7 @@ class CountdownTimer(Thread):
while time_left and not self.stop_event.is_set():
time.sleep(1)
time_left -= 1
if self.timer_tick_callback:
if self.timer_tick_callback and not self.stop_event.is_set():
self.timer_tick_callback(time_left)
if time_left == 0 and self.timer_done_callback:

View File

@ -5,7 +5,12 @@ from typing import Optional
from hopper.countdown_timer import CountdownTimer
from hopper.enums import Direction, GameState, PlayerMoveResult, PlayerState
from hopper.errors import Collision, GameLockForMovement, PositionOutOfBounds
from hopper.errors import (
Collision,
GameLockForMovement,
PositionOutOfBounds,
PurchaseForbiddenForPlayer,
)
from hopper.models.board import (
BOARD_DUMP_CHARS,
BoardLayout,
@ -17,6 +22,7 @@ from hopper.models.board import (
create_random_position,
)
from hopper.models.player import Player, PlayerList, Position
from hopper.models.product import Product
from hopper.watchdog import InactivityWatchdog
from hopper.ws_server import WSServer
from settings import settings
@ -82,9 +88,11 @@ class GameEngine:
self.__debug_print_board()
await self.ws_server.send_game_dump()
def reset_game(self) -> None:
async def reset_game(self) -> None:
self.__debug_print_board()
self.game_state = GameState.RUNNING
self.players.clear()
await self.send_game_dump()
async def start_game_for_player(self, player_name: str) -> Player:
self._start_inactivity_watchdog()
@ -99,11 +107,6 @@ class GameEngine:
self.__debug_print_board()
await self.send_game_dump()
#!!!!!!!!!!!!!!!
await self._player_on_destination(player)
#!!!!!!!!!!!!!!!
await asyncio.sleep(settings.game.MOVE_DELAY)
return player
@ -209,9 +212,21 @@ class GameEngine:
)
self._purchase_countdown_timer.start()
async def purchase_product(self, player: Player, product: Product) -> None:
if not player.state == PlayerState.ON_DESTINATION:
raise PurchaseForbiddenForPlayer()
if self._purchase_countdown_timer:
self._purchase_countdown_timer.stop()
await self.ws_server.send_product_purchase_done_message(
player=player, product=product
)
await self.reset_game()
def _reset_player(self, player) -> None:
# move player to start position
player.position = create_player_start_position(self.board.width, self.board.height)
player.position = create_player_start_position(
self.board.width, self.board.height
)
player.state = PlayerState.CREATED
player.last_seen = None

View File

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

View File

@ -6,7 +6,14 @@ from typing import Optional, TypeVar
from pydantic import Field
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,
ProductDto,
)
from hopper.enums import ObjectType
@ -20,12 +27,6 @@ class LayerDto(BaseModel):
objects: list[LayerObjectDto]
class ProductDto(BaseModel):
name: str
uuid: str
description: Optional[str] = None
class GameDumpDto(BaseModel):
board: BoardDto
destination: DestinationDto