┌─────────────────────────────────────────────────────────┐
│ SSH Clients │
│ (ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost from multiple terminals) │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ SSH Server (port 2222) │
│ (gliderlabs/ssh library) │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Session Handler │
│ (pkg/server/server.go) │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Game Mode Selection Menu │ │
│ │ 1. CPU Mode │ │
│ │ 2. Create Multiplayer Room │ │
│ │ 3. Join Multiplayer Room │ │
│ └──────────────────────────────────────────────┘ │
└─────────┬───────────────────┬────────────────────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│CPU Mode │ │Create │ │Join │
│ │ │Room │ │Room │
└────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ └────────┬───────────┘
│ │
▼ ▼
┌─────────────┐ ┌───────────────────┐
│ Game Logic │ │ Room Manager │
│ (pkg/game) │◄─────────│ (GlobalRM) │
└─────────────┘ └───────────────────┘
File: main.go, pkg/server/server.go
- Listens on port 2222 (configurable)
- Creates session for each connection
- No authentication (public access)
- Handles multiple concurrent connections
File: pkg/server/server.go
- Main menu loop
- Route to game modes
- Input/output formatting
- User interaction flow
File: pkg/game/board.go
- 3x3 board representation
- Move validation
- Win detection (rows, cols, diagonals)
- Draw detection
- Available moves calculation
File: pkg/game/ai.go
Balanced Difficulty Strategy:
1. Can I win? → Make winning move (50% of the time)
2. Can opponent win? → Block them (30% of the time)
3. Is center free? → Take center (40% of the time)
4. Are corners free? → Take random corner (30% of the time)
5. Otherwise → Take any available space (more frequent)
File: pkg/game/multiplayer.go
- Global room manager (singleton)
- Room creation with UUID-based codes
- Player assignment (X/O)
- Turn management
- Thread-safe with mutexes
Player Connects
↓
Choose CPU Mode
↓
Create Board
↓
┌─────────────────┐
│ Game Loop: │
│ 1. Display │
│ 2. Check Win │
│ 3. Get Move │──→ Invalid? → Loop back
│ 4. Apply Move │
│ 5. Check Win │──→ Won? → End game
│ 6. CPU Move │
│ 7. Check Win │──→ Won? → End game
└─────────────────┘
↓
Return to Menu
Player 1 Creates Room Player 2 Joins Room
↓ ↓
Generate Room Code ─────Share Code───→ Enter Code
↓ ↓
Wait for P2 ←─────────Connect─────────┘
↓
┌──────────────────────────────────────────┐
│ Game Loop (Synchronized): │
│ │
│ P1 Turn: │
│ - P1 makes move │
│ - P2 polls for board change │
│ │
│ P2 Turn: │
│ - P2 makes move │
│ - P1 polls for board change │
│ │
│ Check win/draw after each move │
└──────────────────────────────────────────┘
↓
Clean up room
type Room struct {
mutex sync.Mutex // Protects all room state
// ... other fields
}
// All operations lock before modifying:
func (r *Room) MakeMove() {
r.mutex.Lock()
defer r.mutex.Unlock()
// ... safe operations
}type RoomManager struct {
rooms map[string]*Room
mutex sync.RWMutex // Read-write lock
}
// Creates use write lock
// Joins use read lockcells: [9]Cell // Array of 9 cells
// 0-2: Row 1
// 3-5: Row 2
// 6-8: Row 3
Cell values:
- Empty (0)
- X (1)
- O (2)
Room {
ID: string // 6-char code
Board: *Board // Game board
Player1: *Player // X
Player2: *Player // O
Current: Cell // Whose turn
GameOver: bool // Game finished
Winner: Cell // Winner or Empty
}
Client Server
│ │
├──── TCP Connect ─────────────→│
│ │
├──── SSH Handshake ───────────→│
│←──── SSH Response ────────────┤
│ │
│ │
│←──── Welcome Message ─────────┤
│←──── Menu Display ────────────┤
│ │
├──── Menu Choice ─────────────→│
│ │
│←──── Game Board ──────────────┤
│ │
├──── Move Input ──────────────→│
│ │
│←──── Updated Board ───────────┤
│ │
│ (repeat until game ends) │
│ │
├──── Disconnect ──────────────→│
│ │
┌─────────────────────────────────────┐
│ Alpine Linux Container │
│ │
│ ┌───────────────────────────┐ │
│ │ tictactoe-ssh binary │ │
│ │ (static, 6MB) │ │
│ └───────────────────────────┘ │
│ │
│ Exposed Port: 2222 │
└─────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Load │ │ Game │ │ Redis │
│ Balancer │───→│ Server │───→│ (Rooms) │
│ │ │ (Multiple) │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│ PostgreSQL │
│ (Stats/Users)│
└──────────────┘
User Input
↓
Validate Format ──→ Invalid? → Show error, retry
↓
Validate Range ──→ Out of bounds? → Show error, retry
↓
Check Available ──→ Taken? → Show error, retry
↓
Apply Move
↓
Update State
- Latency: < 10ms per move (local network)
- Throughput: Limited by SSH connections (~100-1000 concurrent)
- Memory: ~10MB base + ~1KB per game session
- CPU: Minimal (turn-based, no real-time processing)
- No authentication
- No authorization
- No encryption beyond SSH
- No rate limiting
- Ephemeral rooms (no persistence)
- SSH key authentication
- User management system
- Room access controls
- Input validation and sanitization
- Rate limiting per IP
- Audit logging
- Connection timeouts
- Single server instance
- In-memory room storage
- No load balancing
- No session persistence
- Add Redis for shared room state
- Run multiple server instances
- Add load balancer
- Implement health checks
- Add metrics/monitoring
- Database for persistent data
Unit Tests (board_test.go)
↓
Test Game Logic
├─ Board operations
├─ Win conditions
├─ Move validation
└─ State management
- ✅ Board creation
- ✅ Move validation
- ✅ Win detection (all patterns)
- ✅ Draw detection
- ✅ Available moves
- ❌ AI logic (could add)
- ❌ Multiplayer sync (could add)
- ❌ Integration tests (could add)
Source Code (.go files)
↓
go build
↓
Static Binary (6MB)
↓
Docker Build
↓
Alpine Image (~15MB)
↓
Container Registry
↓
Deployment Target
main.go
├─→ pkg/server
│ └─→ pkg/game
│ ├─→ board.go
│ ├─→ ai.go
│ └─→ multiplayer.go
│
└─→ github.com/gliderlabs/ssh
└─→ golang.org/x/crypto
This architecture balances:
- Simplicity: Easy to understand and modify
- Performance: Fast enough for the use case
- Scalability: Can be extended when needed
- Maintainability: Clean separation of concerns