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:
2026-08-27 05:29:38 +00:00
parent 0e6506d97f
commit e9f5377e0a
9 changed files with 1057 additions and 102 deletions
+91
View File
@@ -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"