704 lines
26 KiB
Markdown
704 lines
26 KiB
Markdown
# MTG Online Multiplayer Game Server — Handoff Document
|
|
|
|
**Project**: MTG Online Web (Modern Web-based MTG Platform)
|
|
**Previous Thread**: Card Backend — Phase 1 & 2 (Deck Building, Card Import, User Data)
|
|
**New Thread**: Phase 3 — Multiplayer Game Server
|
|
**Location**: `/home/wall-o/projects/mtgonline`
|
|
**Date**: 2026-07-25
|
|
|
|
---
|
|
|
|
## Context: What Was Built (Card Backend)
|
|
|
|
The previous thread delivered a complete card management and deckbuilding backend:
|
|
|
|
### Completed Features
|
|
- **FastAPI application** with JWT authentication, dual PostgreSQL databases (`mtgonline` + `mtgdata`), Redis caching
|
|
- **29 REST endpoints** across 10 routers (auth, users, decks, rooms, games, admin, cards, interactions, refresh, user-data, card-import)
|
|
- **16 user-data tables** with Alembic migrations, CASCADE deletes, JSONB flexibility
|
|
- **Card import feature** with fuzzy matching for XLSX/CSV/JSON/ODS files
|
|
- **Deck CRUD** with DRAFT/FINAL status transitions, precedents, suggestions
|
|
- **MTGJSON data pipeline** downloading and upserting card data into `mtgdata` database
|
|
- **Frontend** (React/TypeScript) with deck builder UI, card import, authentication, admin dashboard
|
|
|
|
### Current Architecture
|
|
```
|
|
mtgonline/
|
|
├── backend/ # FastAPI card backend (Phase 1 & 2 complete)
|
|
│ ├── app/
|
|
│ │ ├── core/ # Settings, database engines, Redis client
|
|
│ │ ├── models/ # SQLAlchemy ORM models (user_data, user_deck, card_import)
|
|
│ │ ├── routers/ # API route modules (10 routers)
|
|
│ │ ├── schemas/ # Pydantic schemas
|
|
│ │ ├── services/ # MTGJSON manager, card DB, deck manager, fuzzy matcher
|
|
│ │ └── main.py # FastAPI app entry point
|
|
│ ├── alembic/ # Database migrations
|
|
│ └── Dockerfile
|
|
├── frontend/ # React/TypeScript deck builder (Phase 3 complete)
|
|
├── C++/ # Cockatrice architecture analysis (reference)
|
|
├── state.json # Current state tracking
|
|
└── ROADMAP.md # Full project roadmap
|
|
```
|
|
|
|
### Key Integration Points Available
|
|
- **Card lookup**: `GET /api/cards/{card_id}` — returns full card data for game display
|
|
- **Card search**: `GET /api/cards/search?q=...` — search cards during gameplay
|
|
- **Set lists**: `GET /api/sets/` — list available sets for game formatting
|
|
- **User decks**: `GET /api/users/{user_id}/decks` — retrieve FINAL decks for player selection
|
|
- **Card import status**: `GET /api/v1/card-import/status` — check if user has imported their collection
|
|
- **Database access**: Direct SQLAlchemy async engines for `mtgonline` (app data) and `mtgdata` (card data)
|
|
|
|
---
|
|
|
|
## Phase 3: Multiplayer Game Server — Requirements
|
|
|
|
### Objective
|
|
|
|
Build a real-time multiplayer game server that enables live MTG gameplay between multiple players via WebSocket connections. This is the core gameplay engine — the replacement for Cockatrice's `servatrice` component.
|
|
|
|
### Success Criteria
|
|
|
|
- [ ] Players can create/join game rooms and play MTG in real-time
|
|
- [ ] Game state syncs correctly across all connected players (< 50ms latency)
|
|
- [ ] All 11 MTG turn phases are enforced server-side
|
|
- [ ] Stack resolution works correctly (layers, priority, targeting)
|
|
- [ ] Deck validation ensures legality before gameplay
|
|
- [ ] Game history is recorded as a serialized event log (for replays)
|
|
- [ ] Admin commands work (kick, ban, game control)
|
|
- [ ] Spectator mode is supported
|
|
- [ ] WebSocket reconnection handles gracefully with state sync
|
|
|
|
---
|
|
|
|
## Architecture Blueprint (Derived from Cockatrice Analysis)
|
|
|
|
### Authority Model
|
|
|
|
```
|
|
┌─────────────────┐ WebSocket (JSON) ┌─────────────────┐
|
|
│ CLIENT │◄────────────────────────────────────►│ SERVER │
|
|
│ (React/TS) │ │ (FastAPI/Py) │
|
|
│ │ │ │
|
|
│ • Game Scene │ GameCommands (play, attack, etc.) │ • PostgreSQL │
|
|
│ • Hand View │◄────────────────────────────────────►│ • Game State │
|
|
│ • Chat Panel │ GameEvents (state changes) │ • Room/Player │
|
|
│ • Deck Panel │ │ Management │
|
|
│ • Phase Toolbar │ Chat, Admin, Spectator │ • Replay Log │
|
|
└─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
**Key Principle**: Server is authoritative. Clients send commands, server validates and broadcasts events. No client-side state manipulation.
|
|
|
|
### Game Engine Architecture
|
|
|
|
| Cockatrice Component | Modern Web Equivalent | Role |
|
|
|---|---|---|
|
|
| `AbstractGame` | `Game` (server-side) | Core game instance holding state, players, event handler |
|
|
| `GameMetaInfo` | `GameMetadata` | gameId, maxPlayers, description, started, spectators |
|
|
| `GameState` | `GameBoardState` | currentPhase, activePlayer, hostId, gameTimer |
|
|
| `GameEventHandler` | `GameEventDispatcher` | Central dispatch — processes events, prepares commands |
|
|
| `PlayerManager` | `PlayerRegistry` | Coordinates all players in a game |
|
|
| `PlayerLogic` | `Player` | Per-player game logic |
|
|
| `PlayerActions` | `PlayerCommands` | Concrete commands: play, attack, tap, draw |
|
|
| `CardZone` | `Zone` (base class) | Abstract zone — Hand/Stack/Table/Graveyard/Exile/Library |
|
|
| `HandZone` | `Hand` | Player's hand (secret/hidden zone) |
|
|
| `StackZone` | `Stack` | Spells/abilities on the stack |
|
|
| `TableZone` | `Battlefield` | Permanents on the battlefield |
|
|
| `PileZone` | `Pile` (Graveyard, Exile, Library) | Discard/exile/draw piles |
|
|
| `Replay` | `GameReplay` | Serialized event stream for replay |
|
|
| `Phase` | `TurnPhase` | 11-phase MTG turn structure |
|
|
|
|
### Turn Phase System (11 Phases with Sub-Phases)
|
|
|
|
```
|
|
Untap → Upkeep → Draw → Main 1 → Combat → Main 2 → End → Cleanup
|
|
│
|
|
└── Sub-phases:
|
|
Beginning of Combat
|
|
Declare Attackers
|
|
Declare Blockers
|
|
Combat Damage
|
|
End of Combat
|
|
```
|
|
|
|
### Command/Event Flow
|
|
|
|
```
|
|
User Action (React component)
|
|
│
|
|
▼
|
|
GameCommand (JSON message)
|
|
│
|
|
▼
|
|
GameEventDispatcher.process()
|
|
│
|
|
▼
|
|
Player.handleCommand()
|
|
│
|
|
▼
|
|
ZoneLogic (state mutation)
|
|
│
|
|
▼
|
|
GameEvent (broadcast to all clients via WebSocket)
|
|
│
|
|
▼
|
|
Client receives & updates UI via Zustand/XState
|
|
```
|
|
|
|
### Network Protocol Design
|
|
|
|
Cockatrice uses Protocol Buffers over TCP. Modern web equivalent:
|
|
|
|
| Cockatrice Proto Message | JSON WebSocket Message |
|
|
|---|---|
|
|
| `ServerInfo_Game` | `{ "type": "game_info", "game_id": 1, "max_players": 2, ... }` |
|
|
| `ServerInfo_Player` | `{ "type": "player_info", "player_id": 1, "name": "...", ... }` |
|
|
| `Command_PlayCard` | `{ "type": "cmd_play_card", "card_id": 42, "zone": "hand", ... }` |
|
|
| `Command_Attack` | `{ "type": "cmd_attack", "attacker_id": 7, "targets": [3, 5], ... }` |
|
|
| `Event_Join` | `{ "type": "event_join", "player_id": 2, "properties": {...} }` |
|
|
| `Event_Leave` | `{ "type": "event_leave", "player_id": 1, "reason": "..." }` |
|
|
| `Event_SetActivePlayer` | `{ "type": "event_active_player", "player_id": 1 }` |
|
|
| `Event_SetActivePhase` | `{ "type": "event_active_phase", "phase": 5 }` |
|
|
| `Event_GameSay` | `{ "type": "event_chat", "player_id": 1, "message": "..." }` |
|
|
| `GameReplay` | `{ "type": "replay", "events": [...] }` |
|
|
|
|
### Architecture Patterns to Reuse
|
|
|
|
| Pattern | Cockatrice Usage | Modern Equivalent |
|
|
|---|---|---|
|
|
| **Event Bus** | Qt signals/slots | WebSocket broadcast + event system |
|
|
| **Command Pattern** | `Command_PlayCard`, `Command_Attack` | JSON command messages, validated server-side |
|
|
| **Observer** | `GameEventHandler` emits signals | WebSocket events trigger UI updates |
|
|
| **Strategy** | `CardZoneLogic` subclasses | Zone classes with strategy pattern |
|
|
| **Facade** | `AbstractGame` | Single `Game` object wrapping all subsystems |
|
|
| **Memento** | `DeckListMemento` for undo | Immutable state snapshots for undo/redo |
|
|
| **Repository** | `ServatriceDatabaseInterface` | SQLAlchemy repositories for game state |
|
|
|
|
---
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 3.1: Game Server Foundation
|
|
|
|
#### Deliverables
|
|
1. **Game Server Module** — Separate FastAPI WebSocket endpoint for game rooms
|
|
2. **Game Room Management** — Room creation, player join/leave, spectator support
|
|
3. **Game State Management** — Server-authoritative game state with full phase tracking
|
|
4. **Player System** — Player registration, connection management, disconnection handling
|
|
5. **WebSocket Protocol** — JSON message protocol for commands and events
|
|
6. **Database Models** — Game rooms, player connections, game state persistence
|
|
|
|
#### Files to Create
|
|
```
|
|
backend/app/
|
|
├── models/
|
|
│ ├── game_room.py # Game room model (room_id, game_type, host_id, status)
|
|
│ ├── game_player.py # Player connection (player_id, room_id, connection_id, hand, life)
|
|
│ ├── game_state.py # Game board state (phase, turn, cards, zones)
|
|
│ ├── game_event.py # Game events log (for replay)
|
|
│ └── game_log.py # Game history (serialized event log)
|
|
├── routers/
|
|
│ └── game_server.py # WebSocket endpoint for game rooms
|
|
├── services/
|
|
│ ├── game_engine.py # Core game engine (turn phases, combat, stack)
|
|
│ ├── game_state_manager.py # Game state management and synchronization
|
|
│ ├── player_manager.py # Player management (join, leave, disconnect)
|
|
│ ├── card_zone.py # Card zone logic (hand, stack, battlefield, pile)
|
|
│ ├── game_rule_engine.py # Game rule enforcement (mana, timing, targeting)
|
|
│ └── game_replay.py # Game replay recording and playback
|
|
├── schemas/
|
|
│ └── game_schemas.py # Pydantic schemas for game messages
|
|
└── ws/
|
|
├── connection.py # WebSocket connection management
|
|
└── message_handler.py # Message parsing and dispatch
|
|
```
|
|
|
|
#### Detailed Implementation Steps
|
|
|
|
**Step 1: Game Room Model & Router**
|
|
- Create `GameRoom` model with room_id, game_type, host_id, status (WAITING/PLAYING/FINISHED)
|
|
- Create `GamePlayer` model with player_id, room_id, connection_id, hand (JSONB), life_total
|
|
- Create `GameServer` router with WebSocket endpoint `/ws/game/{room_id}`
|
|
- Implement room creation (host creates room), player join/leave, spectator join/leave
|
|
- Handle WebSocket disconnection gracefully (player_timeout for reconnect)
|
|
|
|
**Step 2: Game State Management**
|
|
- Create `GameBoardState` model tracking: current_phase, active_player, turn_number, host_id
|
|
- Track all zone contents: hands, stack, battlefield, graveyards, exiles, libraries
|
|
- Implement phase transitions (Untap → Upkeep → Draw → Main1 → Combat → Main2 → End → Cleanup)
|
|
- Handle sub-phases within Combat (Beginning, Declare Attackers, Declare Blockers, Combat Damage, End)
|
|
|
|
**Step 3: Card Zone Logic**
|
|
- Implement `CardZone` base class with common operations (add, remove, search, sort)
|
|
- Implement `HandZone` (secret zone, only visible to owner)
|
|
- Implement `StackZone` (priority-based, spell/ability resolution)
|
|
- Implement `BattlefieldZone` (permanent zone, battlefield effects)
|
|
- Implement `PileZone` (Graveyard, Exile, Library — shuffle, draw, discard)
|
|
- Implement zone-specific behaviors (hand: count/mulligan, stack: layer resolution)
|
|
|
|
**Step 4: Game Rule Engine**
|
|
- Implement mana payment tracking and validation
|
|
- Implement timing rules (sorcery speed, instant speed, flash)
|
|
- Implement targeting validation (legal targets, priority chain)
|
|
- Implement combat rules (attackers, blockers, damage assignment, trample, first strike)
|
|
- Implement stack resolution (last in, first out, layer system)
|
|
- Implement life total tracking and loss conditions (0 life, poison, concession)
|
|
|
|
**Step 5: Game Replay System**
|
|
- Serialize all game events as a JSONB log
|
|
- Support replay recording (on game start) and playback (on request)
|
|
- Implement deterministic replay (same commands → same state)
|
|
- Store replays in `game_logs` table with compressed JSONB
|
|
|
|
**Step 6: Admin Commands**
|
|
- Implement kick player command
|
|
- Implement ban player command (from mtgonline DB)
|
|
- Implement game pause/resume
|
|
- Implement spectator mode toggle
|
|
- Implement admin override for game state
|
|
|
|
### Phase 3.2: Integration with Card Backend
|
|
|
|
#### Deliverables
|
|
1. **Deck Validation Service** — Validate decks before game start
|
|
2. **Card Lookup Service** — Fetch card data for game display
|
|
3. **User Authentication** — JWT validation for game server
|
|
4. **Database Integration** — Access to `mtgdata` and `mtgonline` databases
|
|
|
|
#### Integration Points
|
|
- **Card Backend API** (`http://backend:8000`):
|
|
- `GET /api/cards/{card_id}` — Get card details for game board display
|
|
- `GET /api/cards/search?q=...` — Search cards during gameplay
|
|
- `GET /api/sets/` — List available sets for game formatting
|
|
- `GET /api/users/{user_id}/decks` — Get user's FINAL decks for selection
|
|
- **Direct PSQL Access** (via shared connection string):
|
|
- `mtgdata` database — Read card data (cards, sets, etc.)
|
|
- `mtgonline` database — Read user decks (for deck validation, game setup)
|
|
|
|
#### Deck Validation Flow
|
|
```
|
|
Player selects deck → Server fetches deck from mtgonline DB
|
|
│
|
|
▼
|
|
Validate deck against rules:
|
|
│
|
|
├── Check card count (60+ for Standard/Modern, 100+ for Commander)
|
|
│ ├── Card validity (must exist in mtgdata)
|
|
│ ├── Color identity (Commander format)
|
|
│ └── Basic land count (max 4 of each non-basic)
|
|
│
|
|
├── Check banned cards (format-specific ban list)
|
|
│
|
|
└── Return validation result (pass/fail with reasons)
|
|
```
|
|
|
|
### Phase 3.3: WebSocket Client Integration
|
|
|
|
#### Deliverables
|
|
1. **WebSocket Client** — React component for game client
|
|
2. **State Management** — Zustand store for game state
|
|
3. **Message Handlers** — Parse and handle game events
|
|
4. **Reconnection Logic** — Exponential backoff with state sync
|
|
|
|
#### Integration with Frontend
|
|
- **WebSocket Connection**: Connect to `/ws/game/{room_id}` on game start
|
|
- **State Store**: Use Zustand to manage game state, sync with server events
|
|
- **Message Handlers**: Handle join, leave, play_card, attack, etc.
|
|
- **Reconnection**: On disconnect, attempt reconnect with exponential backoff, sync state on reconnect
|
|
|
|
---
|
|
|
|
## Database Schema for Game Server
|
|
|
|
### Tables to Create
|
|
|
|
| Table | Purpose | Key Columns |
|
|
|-------|---------|-------------|
|
|
| `game_rooms` | Game room management | room_id (PK), game_type, host_id, status, created_at |
|
|
| `game_players` | Player connections | player_id (PK), room_id (FK), connection_id, hand (JSONB), life_total, is_spectator |
|
|
| `game_states` | Game board state | state_id (PK), room_id (FK), current_phase, turn_number, active_player, cards (JSONB), created_at |
|
|
| `game_events` | Event log for replay | event_id (PK), room_id (FK), event_type, event_data (JSONB), timestamp |
|
|
| `game_replays` | Recorded games | replay_id (PK), room_id (FK), events (JSONB), duration, started_at, finished_at |
|
|
|
|
### JSONB Structures
|
|
|
|
**Hand**: `{"cards": [{"card_id": 1, "name": "Lightning Bolt", "zone": "hand"}]}`
|
|
**Stack**: `{"items": [{"card_id": 5, "name": "Counterspell", "controller": 1}]}`
|
|
**Battlefield**: `{"cards": [{"card_id": 10, "name": "Craterhoof", "zone": "battlefield", "tapped": false}]}`
|
|
**Graveyard**: `{"cards": [{"card_id": 1, "name": "Lightning Bolt", "zone": "graveyard"}]}`
|
|
|
|
---
|
|
|
|
## Game Engine Implementation Details
|
|
|
|
### Core Game Class
|
|
|
|
```python
|
|
class Game:
|
|
"""Core game instance holding state, players, event handler."""
|
|
|
|
def __init__(self, room_id: str, game_type: str, host_id: int):
|
|
self.room_id = room_id
|
|
self.game_type = game_type # Standard, Modern, Commander
|
|
self.host_id = host_id
|
|
self.players: Dict[int, Player] = {}
|
|
self.state: GameBoardState = GameBoardState()
|
|
self.event_dispatcher: GameEventDispatcher = GameEventDispatcher(self)
|
|
self.replay: GameReplay = GameReplay()
|
|
|
|
def add_player(self, player: Player) -> bool:
|
|
"""Add player to game. Returns True if successful."""
|
|
|
|
def remove_player(self, player_id: int) -> bool:
|
|
"""Remove player from game. Broadcasts event."""
|
|
|
|
def transition_phase(self) -> None:
|
|
"""Advance to next phase. Validates phase transitions."""
|
|
|
|
def handle_command(self, command: GameCommand) -> List[GameEvent]:
|
|
"""Process player command. Returns events to broadcast."""
|
|
|
|
def broadcast_event(self, event: GameEvent) -> None:
|
|
"""Broadcast event to all connected clients."""
|
|
```
|
|
|
|
### Player Class
|
|
|
|
```python
|
|
class Player:
|
|
"""Per-player game logic."""
|
|
|
|
def __init__(self, player_id: int, name: str, deck: Deck):
|
|
self.player_id = player_id
|
|
self.name = name
|
|
self.deck = deck
|
|
self.hand: HandZone = HandZone()
|
|
self.battlefield: BattlefieldZone = BattlefieldZone()
|
|
self.graveyard: PileZone = PileZone()
|
|
self.exile: PileZone = PileZone()
|
|
self.library: LibraryZone = LibraryZone()
|
|
self.life_total: int = 20
|
|
self.poison_counters: int = 0
|
|
|
|
def handle_command(self, command: GameCommand) -> List[GameEvent]:
|
|
"""Process command. Returns events to broadcast."""
|
|
|
|
def play_card(self, card_id: int, zone: str) -> GameEvent:
|
|
"""Play card from hand to battlefield."""
|
|
|
|
def attack(self, attacker_id: int, targets: List[int]) -> GameEvent:
|
|
"""Declare attackers and assign damage."""
|
|
|
|
def tap_card(self, card_id: int) -> GameEvent:
|
|
"""Tap card for mana or attack."""
|
|
|
|
def draw_card(self) -> GameEvent:
|
|
"""Draw card from library."""
|
|
```
|
|
|
|
### Zone Classes
|
|
|
|
```python
|
|
class CardZone:
|
|
"""Abstract zone base class."""
|
|
|
|
def add_card(self, card: Card) -> None:
|
|
"""Add card to zone."""
|
|
|
|
def remove_card(self, card_id: int) -> Card:
|
|
"""Remove card from zone."""
|
|
|
|
def search(self, filter: SearchFilter) -> List[Card]:
|
|
"""Search zone with filter."""
|
|
|
|
def shuffle(self) -> None:
|
|
"""Shuffle zone."""
|
|
|
|
def sort(self) -> None:
|
|
"""Sort zone."""
|
|
|
|
class HandZone(CardZone):
|
|
"""Player's hand (secret zone)."""
|
|
# Only visible to owner
|
|
|
|
class StackZone(CardZone):
|
|
"""Spells/abilities on the stack."""
|
|
# Priority-based resolution
|
|
|
|
class BattlefieldZone(CardZone):
|
|
"""Permanent zone."""
|
|
# Has tap/untap, counters, attachments
|
|
|
|
class PileZone(CardZone):
|
|
"""Graveyard, exile, library."""
|
|
# Shuffleable (library) or non-shuffleable (graveyard)
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
|
|
- **Game State Transitions**: Test all 11 phases and sub-phases
|
|
- **Combat Rules**: Test attack, block, damage assignment, trample, first strike
|
|
- **Stack Resolution**: Test priority, layer system, LIFO resolution
|
|
- **Mana Payment**: Test mana pool, timing, costs
|
|
- **Zone Operations**: Test add/remove/search/shuffle/sort for each zone
|
|
- **Deck Validation**: Test format rules (Standard, Modern, Commander)
|
|
- **Replay Determinism**: Test same commands → same state
|
|
|
|
### Integration Tests
|
|
|
|
- **WebSocket Connection**: Test connect/disconnect/join/leave
|
|
- **Game Room Management**: Test room creation, player count, spectator
|
|
- **Real-time Sync**: Test state sync across multiple clients
|
|
- **Reconnection**: Test reconnect with state sync
|
|
- **Admin Commands**: Test kick, ban, pause, resume
|
|
|
|
### Load Tests
|
|
|
|
- **Concurrent Players**: Test 2-8 players per game
|
|
- **Multiple Games**: Test 100+ concurrent games
|
|
- **Message Throughput**: Test 1000+ messages/sec per game
|
|
- **Latency**: Test < 50ms message latency
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
### Functional Requirements
|
|
|
|
- [ ] Players can create game rooms with custom settings
|
|
- [ ] Players can join existing game rooms
|
|
- [ ] Game starts with all players ready
|
|
- [ ] 11-phase turn structure works correctly
|
|
- [ ] Combat rules enforce attack/block/damage correctly
|
|
- [ ] Stack resolves spells/abilities in correct order
|
|
- [ ] Mana payment validates costs and timing
|
|
- [ ] Deck validation ensures format legality
|
|
- [ ] Game history recorded as serialized event log
|
|
- [ ] Admin can kick/ban players
|
|
- [ ] Spectators can watch games
|
|
- [ ] WebSocket disconnect triggers graceful player timeout
|
|
|
|
### Performance Requirements
|
|
|
|
- [ ] WebSocket latency < 50ms for game state updates
|
|
- [ ] Support 1000+ concurrent users across multiple games
|
|
- [ ] Game state syncs within 100ms across all connected players
|
|
- [ ] Replay recording doesn't impact gameplay performance
|
|
- [ ] Database queries for card lookup < 10ms
|
|
|
|
### Security Requirements
|
|
|
|
- [ ] JWT authentication for all WebSocket connections
|
|
- [ ] Input validation for all game commands
|
|
- [ ] Rate limiting for message spam protection
|
|
- [ ] Admin-only commands require admin role
|
|
- [ ] Game state never exposed to spectators (unless enabled)
|
|
|
|
---
|
|
|
|
## Files to Create/Modify
|
|
|
|
### New Files
|
|
```
|
|
backend/app/
|
|
├── models/
|
|
│ ├── game_room.py
|
|
│ ├── game_player.py
|
|
│ ├── game_state.py
|
|
│ ├── game_event.py
|
|
│ └── game_log.py
|
|
├── routers/
|
|
│ └── game_server.py
|
|
├── services/
|
|
│ ├── game_engine.py
|
|
│ ├── game_state_manager.py
|
|
│ ├── player_manager.py
|
|
│ ├── card_zone.py
|
|
│ ├── game_rule_engine.py
|
|
│ └── game_replay.py
|
|
├── schemas/
|
|
│ └── game_schemas.py
|
|
└── ws/
|
|
├── connection.py
|
|
└── message_handler.py
|
|
```
|
|
|
|
### Modified Files
|
|
```
|
|
backend/app/
|
|
├── main.py # Mount game server router
|
|
├── core/
|
|
│ ├── database.py # Add game database engine if needed
|
|
│ └── settings.py # Add game server settings
|
|
├── alembic/
|
|
│ └── versions/
|
|
│ └── 005_game_server_tables.py
|
|
```
|
|
|
|
### Dependencies to Add
|
|
```
|
|
# backend/requirements.txt
|
|
websockets>=12.0
|
|
pydantic>=2.9.2
|
|
```
|
|
|
|
---
|
|
|
|
## Integration with Card Backend
|
|
|
|
### API Endpoints Used by Game Server
|
|
|
|
| Endpoint | Purpose | Method |
|
|
|----------|---------|--------|
|
|
| `/api/cards/{card_id}` | Get card details | GET |
|
|
| `/api/cards/search?q=...` | Search cards | GET |
|
|
| `/api/sets/` | List sets | GET |
|
|
| `/api/users/{user_id}/decks` | Get user decks | GET |
|
|
| `/api/v1/card-import/status` | Check import status | GET |
|
|
|
|
### Database Access
|
|
|
|
**mtgdata database** (read-only):
|
|
- `mtg_cards` — Card data for game display
|
|
- `mtg_sets` — Set data for format rules
|
|
- `mtg_card_types` — Card type definitions
|
|
|
|
**mtgonline database** (read-write):
|
|
- `users` — User authentication
|
|
- `decks` — User decks for validation
|
|
- `ban` — Ban list for admin commands
|
|
- `game_logs` — Game history storage
|
|
|
|
---
|
|
|
|
## Cockatrice Analysis Reference
|
|
|
|
The complete architecture analysis is at `/home/wall-o/projects/mtgonline/C++/ARCHITECTURE_ANALYSIS.md`.
|
|
|
|
Key takeaways:
|
|
- **Server authoritative**: Never trust client state
|
|
- **Deterministic replay**: Same commands → same state
|
|
- **Zone abstraction**: Each zone independently implementable
|
|
- **Phase system**: 11-phase MTG turn with sub-phases
|
|
- **Command/Event separation**: What player wants vs. what happens
|
|
|
|
The modern web adaptation replaces:
|
|
- TCP with WebSocket (JSON)
|
|
- Protocol Buffers with JSON messages
|
|
- Qt Widgets with React/Next.js
|
|
- C++/MySQL with Python/FastAPI + PostgreSQL
|
|
- Monolithic handler with modular service architecture
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Start with Phase 3.1**: Game server foundation (models, router, WebSocket)
|
|
2. **Implement core game engine**: Turn phases, combat, stack resolution
|
|
3. **Add zone logic**: Hand, stack, battlefield, pile zones
|
|
4. **Integrate with card backend**: Deck validation, card lookup
|
|
5. **Add replay system**: Serialize events for replay
|
|
6. **Test thoroughly**: Unit, integration, load tests
|
|
7. **Update documentation**: API docs, architecture docs
|
|
8. **Push to Gitea**: Commit and push all changes
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Game Room WebSocket Endpoint
|
|
```
|
|
WS /ws/game/{room_id}
|
|
```
|
|
|
|
### Command Messages
|
|
```json
|
|
{
|
|
"type": "cmd_play_card",
|
|
"card_id": 42,
|
|
"zone": "hand",
|
|
"targets": [3, 5],
|
|
"mana_cost": [1, "R", "G"]
|
|
}
|
|
```
|
|
|
|
### Event Messages
|
|
```json
|
|
{
|
|
"type": "event_play_card",
|
|
"card_id": 42,
|
|
"controller": 1,
|
|
"zone": "battlefield",
|
|
"timestamp": 1784948189
|
|
}
|
|
```
|
|
|
|
### State Sync Message
|
|
```json
|
|
{
|
|
"type": "state_sync",
|
|
"game_state": {
|
|
"current_phase": "main1",
|
|
"turn_number": 5,
|
|
"active_player": 1,
|
|
"hands": {"1": [...], "2": [...]},
|
|
"battlefield": [...],
|
|
"stack": [...],
|
|
"graveyards": {"1": [...], "2": [...]}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
Add to `.env` or docker-compose:
|
|
|
|
```bash
|
|
# Game Server
|
|
GAME_SERVER_HOST=0.0.0.0
|
|
GAME_SERVER_PORT=8765
|
|
GAME_WS_PATH=/ws/game
|
|
GAME_TOKEN_EXPIRY=3600
|
|
GAME_PLAYER_TIMEOUT=30
|
|
GAME_MAX_PLAYERS=8
|
|
```
|
|
|
|
---
|
|
|
|
## Docker Configuration
|
|
|
|
Add to `docker-compose.dev.yml`:
|
|
|
|
```yaml
|
|
game-server:
|
|
build:
|
|
context: ./backend
|
|
dockerfile: Dockerfile
|
|
ports:
|
|
- "8765:8765"
|
|
environment:
|
|
- DATABASE_URL=${DATABASE_URL}
|
|
- MTG_DATABASE_URL=${MTG_DATABASE_URL}
|
|
- REDIS_URL=${REDIS_URL}
|
|
- GAME_SERVER_PORT=8765
|
|
- GAME_WS_PATH=/ws/game
|
|
depends_on:
|
|
- postgres
|
|
- mtgdata
|
|
- redis
|
|
networks:
|
|
- mtgonline
|
|
```
|
|
|
|
---
|
|
|
|
**Last Updated**: 2026-07-25
|
|
**Status**: Handoff complete. Ready for Phase 3 implementation.
|
|
**Next Action**: Begin Phase 3.1 — Game server foundation (models, router, WebSocket).
|