Project rename and restructure
This commit is contained in:
0
hopper/api/__init__.py
Normal file
0
hopper/api/__init__.py
Normal file
13
hopper/api/dependencies.py
Normal file
13
hopper/api/dependencies.py
Normal file
@ -0,0 +1,13 @@
|
||||
from hopper.engine import GameEngine, GameEngineFactory
|
||||
|
||||
game_engine: GameEngine
|
||||
|
||||
|
||||
def create_game_engine() -> GameEngine:
|
||||
global game_engine
|
||||
game_engine = GameEngineFactory.create_default()
|
||||
return game_engine
|
||||
|
||||
|
||||
def get_game_engine() -> GameEngine:
|
||||
return game_engine
|
||||
73
hopper/api/dto.py
Normal file
73
hopper/api/dto.py
Normal file
@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel as PydanticBaseModel
|
||||
|
||||
from hopper.models.board import GameBoard
|
||||
from hopper.models.player import Player, Position
|
||||
|
||||
|
||||
class BaseModel(PydanticBaseModel):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class PingResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class BoardDto(BaseModel):
|
||||
width: int
|
||||
height: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(board: GameBoard) -> BoardDto:
|
||||
return BoardDto.from_orm(board)
|
||||
|
||||
|
||||
class PositionDto(BaseModel):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(position: Position) -> PositionDto:
|
||||
return PositionDto.from_orm(position)
|
||||
|
||||
|
||||
class PlayerDto(BaseModel):
|
||||
uuid: str
|
||||
position: PositionDto
|
||||
move_count: int
|
||||
move_attempt_count: int
|
||||
|
||||
@staticmethod
|
||||
def from_model(player: Player) -> PlayerDto:
|
||||
return PlayerDto.from_orm(player)
|
||||
|
||||
|
||||
class DestinationDto(BaseModel):
|
||||
position: PositionDto
|
||||
|
||||
|
||||
class StartGameRequestDto(BaseModel):
|
||||
player_name: str
|
||||
|
||||
|
||||
class GameInfoDto(BaseModel):
|
||||
board: BoardDto
|
||||
destination: DestinationDto
|
||||
|
||||
|
||||
class StartGameResponseDto(GameInfoDto):
|
||||
player: PlayerDto
|
||||
|
||||
|
||||
class MovePlayerResponseDto(BaseModel):
|
||||
player: PlayerDto
|
||||
|
||||
|
||||
class PlayerInfoResponseDto(MovePlayerResponseDto):
|
||||
...
|
||||
|
||||
|
||||
class ErrorResponseDto(BaseModel):
|
||||
detail: str
|
||||
132
hopper/api/views.py
Normal file
132
hopper/api/views.py
Normal file
@ -0,0 +1,132 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||
from starlette import status
|
||||
|
||||
from hopper.api.dependencies import get_game_engine
|
||||
from hopper.api.dto import (
|
||||
BoardDto,
|
||||
DestinationDto,
|
||||
ErrorResponseDto,
|
||||
GameInfoDto,
|
||||
MovePlayerResponseDto,
|
||||
PingResponse,
|
||||
PlayerDto,
|
||||
PlayerInfoResponseDto,
|
||||
PositionDto,
|
||||
StartGameRequestDto,
|
||||
StartGameResponseDto,
|
||||
)
|
||||
from hopper.engine import GameEngine
|
||||
from hopper.enums import Direction, PlayerMoveResult
|
||||
from hopper.errors import Collision, PositionOutOfBounds
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@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=BoardDto.from_model(engine.board),
|
||||
destination=DestinationDto(
|
||||
position=PositionDto.from_model(engine.board.destination.position)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/game", response_model=StartGameResponseDto)
|
||||
async def start_game(
|
||||
body: StartGameRequestDto,
|
||||
engine: GameEngine = Depends(get_game_engine),
|
||||
) -> StartGameResponseDto:
|
||||
new_player = engine.start_game(player_name=body.player_name)
|
||||
|
||||
return StartGameResponseDto(
|
||||
board=BoardDto.from_model(engine.board),
|
||||
player=PlayerDto.from_model(new_player),
|
||||
destination=DestinationDto(
|
||||
position=PositionDto.from_model(engine.board.destination.position)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/player/{uuid}",
|
||||
response_model=PlayerInfoResponseDto,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def get_player_info(
|
||||
uuid: str,
|
||||
engine: GameEngine = Depends(get_game_engine),
|
||||
) -> MovePlayerResponseDto:
|
||||
player = engine.players.find(uuid)
|
||||
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_404_NOT_FOUND,
|
||||
detail="Player kicked out due to inactivity",
|
||||
)
|
||||
return PlayerInfoResponseDto(player=PlayerDto.from_model(player))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/player/{uuid}/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 uuid not valid, probably due to inactivity",
|
||||
},
|
||||
status.HTTP_409_CONFLICT: {
|
||||
"model": ErrorResponseDto,
|
||||
"description": " Position out of bounds or collision with an object",
|
||||
},
|
||||
},
|
||||
)
|
||||
async def move_player(
|
||||
uuid: str,
|
||||
direction: Direction,
|
||||
response: Response,
|
||||
engine: GameEngine = Depends(get_game_engine),
|
||||
) -> MovePlayerResponseDto:
|
||||
player = engine.players.find(uuid)
|
||||
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_404_NOT_FOUND,
|
||||
detail="Player kicked out due to inactivity",
|
||||
)
|
||||
|
||||
try:
|
||||
move_result = 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"
|
||||
)
|
||||
|
||||
if move_result == PlayerMoveResult.DESTINATION_REACHED:
|
||||
response.status_code = status.HTTP_200_OK
|
||||
|
||||
return MovePlayerResponseDto(player=PlayerDto.from_model(player))
|
||||
Reference in New Issue
Block a user