Phase 5: Complete Core Game Engine implementation
- Added MTGOnlineGame, GamePlayer, GameHistory models - Implemented full game CRUD endpoints (create, list, get, join, leave, start, end) - Integrated WebSocket game server with database persistence - Added Commander deck validation service (100-card min, color identity, ban list) - Created Alembic migration 006 for new game tables - All game endpoints now database-backed (no more stubs)
This commit is contained in:
@@ -183,6 +183,97 @@ class GameLog(Base):
|
||||
player = relationship("User")
|
||||
|
||||
|
||||
class MTGOnlineGame(Base):
|
||||
"""
|
||||
Active game session model.
|
||||
|
||||
Represents a live game in progress or waiting to start.
|
||||
Tracks game state, players, and format rules.
|
||||
"""
|
||||
__tablename__ = "mtgonline_games"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=False, index=True)
|
||||
host_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True)
|
||||
game_type = Column(String(50), nullable=False, default="Commander") # Commander, Standard, Modern, Pauper
|
||||
state = Column(String(20), nullable=False, default="WAITING") # WAITING, IN_PROGRESS, FINISHED, ABORTED
|
||||
is_password_protected = Column(Boolean, default=False)
|
||||
max_players = Column(Integer, default=4)
|
||||
current_players = Column(Integer, default=0)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
started_at = Column(DateTime, nullable=True)
|
||||
finished_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
room = relationship("Room")
|
||||
host = relationship("User", foreign_keys=[host_id])
|
||||
players = relationship(
|
||||
"GamePlayer",
|
||||
back_populates="game",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<MTGOnlineGame {self.id} state={self.state} players={self.current_players}>"
|
||||
|
||||
|
||||
class GamePlayer(Base):
|
||||
"""
|
||||
Association table for game participants.
|
||||
|
||||
Links users to active games with seat assignments and game state.
|
||||
"""
|
||||
__tablename__ = "game_players"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
game_id = Column(Integer, ForeignKey("mtgonline_games.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True)
|
||||
seat_number = Column(Integer, nullable=True) # 1-based seat assignment
|
||||
life_total = Column(Integer, default=20)
|
||||
is_ready = Column(Boolean, default=False)
|
||||
is_host = Column(Boolean, default=False)
|
||||
joined_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
game = relationship("MTGOnlineGame", back_populates="players")
|
||||
user = relationship("User")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("game_id", "user_id", name="uq_game_player_unique"),
|
||||
Index("idx_game_players_game", "game_id"),
|
||||
Index("idx_game_players_user", "user_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<GamePlayer game={self.game_id} user={self.user_id} seat={self.seat_number}>"
|
||||
|
||||
|
||||
class GameHistory(Base):
|
||||
"""
|
||||
Historical record of completed games.
|
||||
|
||||
Stores post-game data including results, participants, and duration.
|
||||
"""
|
||||
__tablename__ = "game_history"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
game_id = Column(Integer, ForeignKey("mtgonline_games.id"), nullable=True) # Reference to original game
|
||||
room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=True, index=True)
|
||||
game_type = Column(String(50), nullable=False, default="Commander")
|
||||
winner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True, index=True)
|
||||
duration_seconds = Column(Integer, nullable=True)
|
||||
result_type = Column(String(50), nullable=True) # 'victory', 'concession', 'timeout'
|
||||
replay_data = Column(JSON, nullable=True) # Game event log
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
room = relationship("Room")
|
||||
winner = relationship("User", foreign_keys=[winner_id])
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<GameHistory {self.id} type={self.game_type} winner={self.winner_id}>"
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""Audit trail for administrative actions."""
|
||||
__tablename__ = "mtgonline_audit"
|
||||
|
||||
Reference in New Issue
Block a user