Project rename and restructure

This commit is contained in:
Eden Kirin
2023-03-25 13:21:07 +01:00
commit 0041b7d43e
21 changed files with 1328 additions and 0 deletions

0
hopper/api/__init__.py Normal file
View File

View 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
View 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
View 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))