Turn-Based Multiplayer Development for Mobile Games
Turn-based multiplayer is simpler than real-time for networking, but more complex for UX and server logic. Players take turns, connection doesn't need to stay open—but must ensure correct move order, server validation, notifications, and session recovery after exit.
Server-Side Move Validation
Main mistake—trusting client validation. Client checks "move is valid" and sends to server. Cheater intercepts traffic and sends invalid move directly. Server applies. Result—broken game state.
Correct scheme: server stores authoritative game state. Client sends intent (moveFrom: e2, moveTo: e4 in chess), server validates per rules, applies, broadcasts new state to all participants. Client only renders what came from server.
In practice: game rules logic lives on server. For Unity—separate headless server project or microservice in Go/Node.js with ported game logic.
Session Management and Push Notifications
Turn-based doesn't need constant connection. After move, player closes app. Next opponent's move should arrive via push notification: FCM on Android, APNs on iOS.
Server stores device token, on move change sends notification via FCM sendMulticast or APNs HTTP/2. Client on notification tap opens specific game session—needs deep link with game_session_id.
Firebase Cloud Messaging on Android—FirebaseMessagingService, override onMessageReceived. On iOS—UNUserNotificationCenter + UNNotificationRequest. Important: iOS needs content-available: 1 for background state refresh without banner.
State Recovery
Player exits mid-match. Server stores full move log (event sourcing). On reconnect client gets GameStateSnapshot—current state—and renders without replay history. History needed only for move journal display.
Move timeout: server starts timer after move change. If player doesn't move in N minutes—auto-move or timeout loss. Implementation via ScheduledExecutorService on JVM backend or setTimeout in Node.js with jobId in Redis.
Matchmaking for Turn-Based Games
ELO-based matchmaking: on search start, client sends findMatch with current rating. Server searches queue for player with rating ±150 points. If not found in 30 sec—expands to ±300. At 60 sec—offers match with bot.
Queue implementation: Redis Sorted Set where score = player rating. Search range via ZRANGEBYSCORE. Atomicity critical: two matchmakers can't claim same player twice. Lua script in Redis—only atomic "find and delete":
local candidates = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[2], 'LIMIT', 0, 1)
if #candidates > 0 then
redis.call('ZREM', KEYS[1], candidates[1])
return candidates[1]
end
return nil
Without this, at horizontal scaling, same player enters two matches simultaneously.
Timeline
2-player turn-based multiplayer with basic matchmaking, push notifications, and server validation: 3-6 weeks. Room support, observers, asynchronous matches—adds 2-3 weeks. Cost calculated individually.







