Розробка асинхронного мультиплеєра для мобільних ігор
Асинхронний мультиплеєр—варіант пошагового, де обидва гравці не онлайн одночасно. Класичні приклади: Words with Friends, Clash Royale Trophy Road з replay-атаками, будь-які ігри формату «хід за день». Гравець робить хід, дані йдуть на сервер, опонент заходить через кілька годин і бачить оновлений стан.
Зберігання стану та версіонування
Кожен хід—транзакція БД. Структура запису: game_id, move_number, player_id, action_payload (JSON), timestamp, resulting_state_hash. Hash стану після кожного ходу дозволяє виявити розходження: клієнт пересчитує hash та порівнює з серверним. Якщо не збігається—запитує повний снимок.
Event sourcing тут краще, ніж збереження тільки поточного стану: можна відновити будь-який момент гри, реалізувати replay, аудит та відкат при виявленні баґу у логіці.
Синхронізація через polling та WebSocket
При старті сесії клієнт робить GET /game/{id}/state та отримує поточний стан з номером version. Два варіанти сповіщень про хід опонента: polling кожні 30-60 секунд або WebSocket/SSE при відкритому застосунку + push при закритому.
Polling простіше, але навантажує сервер. Краще: WebSocket при відкритому з подіями game_state_updated. При закритому—FCM/APNs push. При отриманні push—клієнт відкриває гру та робить GET /game/{id}/state?since_version={lastKnown}—отримує тільки зміни з останньої відомої версії.
Offline-first UX
Користувач робить хід без інтернету—хід зберігається локально та надсилається при відновленні з'єднання. На Android—WorkManager з NetworkType.CONNECTED: завдання виконується, як тільки з'явиться інтернет, навіть якщо застосунок закритий.
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val submitMoveRequest = OneTimeWorkRequestBuilder<SubmitMoveWorker>()
.setConstraints(constraints)
.setInputData(workDataOf("move_payload" to moveJson))
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(submitMoveRequest)
На iOS—BGProcessingTask з requiresNetworkConnectivity = true. Хід зберігається у Core Data, завдання надсилає при першій можливості.
Конфлікти: якщо обидва гравці якимось чином одночасно надсилають хід (баґ клієнта), сервер приймає тільки перший за timestamp та повертає помилку другому з поточним станом.
Графік
Базова асинхронна система для 2 гравців з event sourcing, offline-first та push-сповіщеннями: 2-4 тижні. Вартість розраховується індивідуально після аналізу ігрової механіки.







