Add REST API endpoints for game lifecycle operations:
- POST /games - Create new game between two players
- GET /games/{game_id} - Get game info for reconnection
- GET /games/me/active - List user's active games
- POST /games/{game_id}/resign - Resign from game via HTTP
Includes proper reverse proxy support for WebSocket URL generation
(X-Forwarded-* headers -> settings.base_url -> Host header fallback).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
161 lines
6.3 KiB
Python
161 lines
6.3 KiB
Python
"""Game schemas for Mantimon TCG.
|
|
|
|
This module defines Pydantic models for game-related API requests
|
|
and responses. Games are real-time matches between players.
|
|
|
|
The backend is stateless - game rules come from the request via RulesConfig.
|
|
Frontend provides the rules appropriate for the game mode (campaign, freeplay, custom).
|
|
|
|
Example:
|
|
game = GameInfoResponse(
|
|
game_id="abc-123",
|
|
game_type=GameType.FREEPLAY,
|
|
player1_id=uuid4(),
|
|
player2_id=uuid4(),
|
|
current_player_id="player1-id",
|
|
turn_number=5,
|
|
phase=TurnPhase.MAIN,
|
|
is_your_turn=True,
|
|
started_at=datetime.now(UTC),
|
|
)
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.core.config import RulesConfig
|
|
from app.core.enums import GameEndReason, TurnPhase
|
|
from app.db.models.game import GameType
|
|
|
|
|
|
class GameCreateRequest(BaseModel):
|
|
"""Request model for creating a new game.
|
|
|
|
For multiplayer games, opponent_id should be provided (Phase 6 matchmaking).
|
|
For campaign games, npc_id should be provided (Phase 5).
|
|
Currently supports direct opponent specification for testing.
|
|
|
|
Attributes:
|
|
deck_id: ID of the deck to use for this game.
|
|
opponent_id: UUID of the opponent player (for PvP games).
|
|
opponent_deck_id: UUID of opponent's deck (required if opponent_id provided).
|
|
rules_config: Game rules from the frontend (defaults to standard rules).
|
|
game_type: Type of game (freeplay, ranked, etc.).
|
|
"""
|
|
|
|
deck_id: UUID = Field(..., description="ID of deck to use")
|
|
opponent_id: UUID = Field(..., description="Opponent player ID")
|
|
opponent_deck_id: UUID = Field(..., description="Opponent's deck ID")
|
|
rules_config: RulesConfig = Field(
|
|
default_factory=RulesConfig, description="Game rules from frontend"
|
|
)
|
|
game_type: GameType = Field(default=GameType.FREEPLAY, description="Type of game")
|
|
|
|
|
|
class GameCreateResponse(BaseModel):
|
|
"""Response model for game creation.
|
|
|
|
Attributes:
|
|
game_id: Unique game identifier.
|
|
ws_url: WebSocket URL to connect for gameplay.
|
|
starting_player_id: ID of the player who goes first.
|
|
message: Status message.
|
|
"""
|
|
|
|
game_id: str = Field(..., description="Unique game identifier")
|
|
ws_url: str = Field(..., description="WebSocket URL for gameplay")
|
|
starting_player_id: str = Field(..., description="Player who goes first")
|
|
message: str = Field(default="Game created successfully", description="Status message")
|
|
|
|
|
|
class GameInfoResponse(BaseModel):
|
|
"""Response model for game info.
|
|
|
|
Provides metadata about a game without full state (use WebSocket for state).
|
|
|
|
Attributes:
|
|
game_id: Unique game identifier.
|
|
game_type: Type of game (campaign, freeplay, ranked).
|
|
player1_id: First player's user ID.
|
|
player2_id: Second player's user ID (None for campaign).
|
|
npc_id: NPC opponent ID for campaign games.
|
|
current_player_id: ID of player whose turn it is.
|
|
turn_number: Current turn number.
|
|
phase: Current turn phase.
|
|
is_your_turn: Whether it's the requesting user's turn.
|
|
is_game_over: Whether the game has ended.
|
|
winner_id: Winner's ID if game ended (None for ongoing or draw).
|
|
end_reason: Reason game ended, if applicable.
|
|
started_at: When the game started.
|
|
last_action_at: Timestamp of last action.
|
|
"""
|
|
|
|
game_id: str = Field(..., description="Unique game identifier")
|
|
game_type: GameType = Field(..., description="Type of game")
|
|
player1_id: UUID = Field(..., description="First player's user ID")
|
|
player2_id: UUID | None = Field(default=None, description="Second player's user ID")
|
|
npc_id: str | None = Field(default=None, description="NPC opponent ID")
|
|
current_player_id: str | None = Field(default=None, description="Current player's turn")
|
|
turn_number: int = Field(default=0, description="Current turn number")
|
|
phase: TurnPhase | None = Field(default=None, description="Current turn phase")
|
|
is_your_turn: bool = Field(default=False, description="Is it your turn")
|
|
is_game_over: bool = Field(default=False, description="Has game ended")
|
|
winner_id: str | None = Field(default=None, description="Winner's ID if ended")
|
|
end_reason: GameEndReason | None = Field(default=None, description="Why game ended")
|
|
started_at: datetime = Field(..., description="Game start time")
|
|
last_action_at: datetime = Field(..., description="Last action time")
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ActiveGameSummary(BaseModel):
|
|
"""Summary of an active game for list endpoints.
|
|
|
|
Lighter-weight than GameInfoResponse for list views.
|
|
|
|
Attributes:
|
|
game_id: Unique game identifier.
|
|
game_type: Type of game.
|
|
opponent_name: Opponent's display name or NPC name.
|
|
is_your_turn: Whether it's your turn.
|
|
turn_number: Current turn number.
|
|
started_at: When the game started.
|
|
last_action_at: Timestamp of last action.
|
|
"""
|
|
|
|
game_id: str = Field(..., description="Unique game identifier")
|
|
game_type: GameType = Field(..., description="Type of game")
|
|
opponent_name: str = Field(..., description="Opponent's name")
|
|
is_your_turn: bool = Field(default=False, description="Is it your turn")
|
|
turn_number: int = Field(default=0, description="Current turn number")
|
|
started_at: datetime = Field(..., description="Game start time")
|
|
last_action_at: datetime = Field(..., description="Last action time")
|
|
|
|
|
|
class ActiveGameListResponse(BaseModel):
|
|
"""Response model for listing user's active games.
|
|
|
|
Attributes:
|
|
games: List of active game summaries.
|
|
total: Total number of active games.
|
|
"""
|
|
|
|
games: list[ActiveGameSummary] = Field(default_factory=list, description="Active games")
|
|
total: int = Field(default=0, ge=0, description="Total active games")
|
|
|
|
|
|
class GameResignResponse(BaseModel):
|
|
"""Response model for game resignation.
|
|
|
|
Attributes:
|
|
success: Whether resignation was successful.
|
|
game_id: The game that was resigned from.
|
|
message: Status message.
|
|
"""
|
|
|
|
success: bool = Field(..., description="Was resignation successful")
|
|
game_id: str = Field(..., description="Game ID")
|
|
message: str = Field(default="", description="Status message")
|