from fastapi import APIRouter, Depends, HTTPException, Response from starlette import status from hopper.api.dependencies import get_game_engine 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, PurchaseForbiddenForPlayer, ) from hopper.models.player import Player from settings import settings router = APIRouter() def get_player(id: str, engine: GameEngine = Depends(get_game_engine)) -> Player: player = engine.players.find(id) if player is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Player not found" ) if not player.active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Player kicked out due to inactivity", ) return player @router.get("/ping", response_model=PingResponse) async def ping() -> PingResponse: return PingResponse( message="Pong!", ) @router.get("/game", response_model=GameInfoDto) async def get_game_info( engine: GameEngine = Depends(get_game_engine), ) -> GameInfoDto: return GameInfoDto( board=engine.board, destination=DestinationDto( position=engine.board.destination.position, ), ) @router.post( "/game", response_model=StartGameResponseDto, status_code=status.HTTP_201_CREATED ) async def start_game( body: StartGameRequestDto, engine: GameEngine = Depends(get_game_engine), ) -> StartGameResponseDto: new_player = await engine.start_game_for_player(player_name=body.player_name) return StartGameResponseDto( board=engine.board, player=new_player, destination=DestinationDto( position=engine.board.destination.position, ), ) @router.get( "/player/{id}", response_model=PlayerInfoResponseDto, responses={ status.HTTP_403_FORBIDDEN: { "model": ErrorResponseDto, "description": "Player inactive", }, status.HTTP_404_NOT_FOUND: { "model": ErrorResponseDto, "description": "Player with id not found, probably kicked out", }, }, ) async def get_player_info( player: Player = Depends(get_player), ) -> MovePlayerResponseDto: return PlayerInfoResponseDto(player=player) @router.post( "/player/{id}/move/{direction}", response_model=MovePlayerResponseDto, status_code=status.HTTP_201_CREATED, responses={ status.HTTP_200_OK: { "model": MovePlayerResponseDto, "description": "Destination reached!", }, status.HTTP_403_FORBIDDEN: { "model": ErrorResponseDto, "description": "Player inactive", }, status.HTTP_404_NOT_FOUND: { "model": ErrorResponseDto, "description": "Player with id not found, probably kicked out", }, status.HTTP_409_CONFLICT: { "model": ErrorResponseDto, "description": "Position out of bounds or collision with an object", }, status.HTTP_423_LOCKED: { "model": ErrorResponseDto, "description": "Player reached destination. Can't move anymore.", }, }, ) async def move_player( direction: Direction, response: Response, engine: GameEngine = Depends(get_game_engine), player: Player = Depends(get_player), ) -> MovePlayerResponseDto: try: move_result = await engine.move_player(player, direction) except PositionOutOfBounds: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Position out of bounds" ) except Collision: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Collision with an object" ) except GameLockForMovement: raise HTTPException( status_code=status.HTTP_423_LOCKED, detail="Player reached destination. Can't move anymore.", ) if move_result == PlayerMoveResult.DESTINATION_REACHED: 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/{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" )