diff --git a/androidApp/src/main/kotlin/com/livrets/androidApp/PlayGamesServices.kt b/androidApp/src/main/kotlin/com/livrets/androidApp/PlayGamesServices.kt index 52f37ca..e989c51 100644 --- a/androidApp/src/main/kotlin/com/livrets/androidApp/PlayGamesServices.kt +++ b/androidApp/src/main/kotlin/com/livrets/androidApp/PlayGamesServices.kt @@ -2,6 +2,7 @@ package com.livrets.androidApp import android.app.Activity import android.content.Intent +import android.util.Log import com.google.android.gms.games.PlayGames import com.livrets.games.GamesServices import kotlinx.coroutines.Dispatchers @@ -16,19 +17,20 @@ class PlayGamesServices( override suspend fun signIn(): Boolean = withContext(Dispatchers.IO) { runCatching { PlayGames.getGamesSignInClient(activity).signIn().await().isAuthenticated - }.getOrDefault(false) + }.onFailure { Log.w(TAG, "Échec connexion Play Jeux", it) }.getOrDefault(false) } override suspend fun isSignedIn(): Boolean = withContext(Dispatchers.IO) { runCatching { PlayGames.getGamesSignInClient(activity).isAuthenticated().await().isAuthenticated - }.getOrDefault(false) + }.onFailure { Log.w(TAG, "Échec vérification connexion Play Jeux", it) } + .getOrDefault(false) } override suspend fun playerDisplayName(): String? = withContext(Dispatchers.IO) { runCatching { PlayGames.getPlayersClient(activity).currentPlayer.await().displayName - }.getOrNull() + }.onFailure { Log.w(TAG, "Échec récupération du joueur", it) }.getOrNull() } override suspend fun submitScore(leaderboardId: String, score: Long): Boolean = @@ -38,7 +40,8 @@ class PlayGamesServices( .submitScoreImmediate(leaderboardId, score) .await() true - }.getOrDefault(false) + }.onFailure { Log.w(TAG, "Échec envoi du score au board $leaderboardId", it) } + .getOrDefault(false) } override suspend fun showLeaderboard(leaderboardId: String) { @@ -47,8 +50,13 @@ class PlayGamesServices( PlayGames.getLeaderboardsClient(activity) .getLeaderboardIntent(leaderboardId) .await() - }.getOrNull() + }.onFailure { Log.w(TAG, "Échec ouverture du board $leaderboardId", it) } + .getOrNull() } ?: return launchIntent(intent) } + + private companion object { + const val TAG = "PlayGamesServices" + } } diff --git a/docs/superpowers/specs/2026-08-28-play-games-services-design.md b/docs/superpowers/specs/2026-08-28-play-games-services-design.md index e10237f..ccd504c 100644 --- a/docs/superpowers/specs/2026-08-28-play-games-services-design.md +++ b/docs/superpowers/specs/2026-08-28-play-games-services-design.md @@ -92,16 +92,19 @@ interface GamesServices { 23 à 24 — abandon des appareils Android 6, décision produit du 2026-08-28). - La dépendance GMS reste confinée à `androidApp` ; sharedUI ne dépend que de l'interface (aucun impact desktop/iOS). -- Les IDs de leaderboard sont définis à la création en console (IDs custom - `questions_5` … `questions_50`), donc le mapping commonMain est trivial et stable. +- Les IDs de leaderboard sont générés automatiquement par la console (format `CgkI…`, + non personnalisables — correction 2026-08-31 : le mapping commonMain est donc une + table en dur des IDs réels, `GameConfig.leaderboardId(): String?`, `null` si le + nombre de questions n'a pas de board). ## 7. Prérequis Play Console (manuel, hors code) 1. Créer l'app dans Play Console (package `com.livrets.androidApp`). 2. Jouer → Play Jeux Services : créer le jeu, lier l'app Android, ajouter les empreintes SHA-1 debug et release. -3. Créer les 10 leaderboards avec IDs `questions_5` … `questions_50` - (« 5 questions » … « 50 questions »). +3. Créer les 10 leaderboards (« 5 questions » … « 50 questions ») ; la console génère + les IDs (`CgkI…`), à reporter dans `Leaderboards.kt` (correction 2026-08-31 : pas + d'ID personnalisable). 4. Ajouter des testeurs PGS pour valider la connexion en développement. Sans ce setup, toutes les opérations échouent silencieusement : aucune régression diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/App.kt b/sharedUI/src/commonMain/kotlin/com/livrets/App.kt index da3d21c..959892b 100644 --- a/sharedUI/src/commonMain/kotlin/com/livrets/App.kt +++ b/sharedUI/src/commonMain/kotlin/com/livrets/App.kt @@ -74,7 +74,7 @@ fun App( scope.launch { if (gamesServices.isSignedIn() || gamesServices.signIn()) { if (playerName == null) playerName = gamesServices.playerDisplayName() - gamesServices.showLeaderboard(config.leaderboardId()) + config.leaderboardId()?.let { gamesServices.showLeaderboard(it) } } } }, @@ -91,7 +91,9 @@ fun App( withContext(NonCancellable) { val score = result.leaderboardScore() if (score > 0 && (gamesServices.isSignedIn() || gamesServices.signIn())) { - gamesServices.submitScore(result.config.leaderboardId(), score) + result.config.leaderboardId()?.let { + gamesServices.submitScore(it, score) + } } } } diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt b/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt index bada4ea..ecedd66 100644 --- a/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt +++ b/sharedUI/src/commonMain/kotlin/com/livrets/games/Leaderboards.kt @@ -2,4 +2,17 @@ package com.livrets.games import com.livrets.game.GameConfig -fun GameConfig.leaderboardId(): String = "questions_$questionCount" +private val leaderboardIds = mapOf( + 5 to "CgkIuqy3oN8HEAIQAg", + 10 to "CgkIuqy3oN8HEAIQAw", + 15 to "CgkIuqy3oN8HEAIQBA", + 20 to "CgkIuqy3oN8HEAIQBQ", + 25 to "CgkIuqy3oN8HEAIQBg", + 30 to "CgkIuqy3oN8HEAIQBw", + 35 to "CgkIuqy3oN8HEAIQCA", + 40 to "CgkIuqy3oN8HEAIQCQ", + 45 to "CgkIuqy3oN8HEAIQCg", + 50 to "CgkIuqy3oN8HEAIQCw", +) + +fun GameConfig.leaderboardId(): String? = leaderboardIds[questionCount] diff --git a/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt b/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt index 4d619a6..8645587 100644 --- a/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt +++ b/sharedUI/src/commonTest/kotlin/com/livrets/games/LeaderboardScoreTest.kt @@ -7,6 +7,7 @@ import com.livrets.game.Question import com.livrets.game.product import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull import kotlin.time.Instant class LeaderboardScoreTest { @@ -84,7 +85,26 @@ class LeaderboardScoreTest { @Test fun `id de board par nombre de questions`() { - assertEquals("questions_10", GameConfig(10, 5, 12).leaderboardId()) - assertEquals("questions_5", GameConfig(5, 3, 1).leaderboardId()) + val attendu = mapOf( + 5 to "CgkIuqy3oN8HEAIQAg", + 10 to "CgkIuqy3oN8HEAIQAw", + 15 to "CgkIuqy3oN8HEAIQBA", + 20 to "CgkIuqy3oN8HEAIQBQ", + 25 to "CgkIuqy3oN8HEAIQBg", + 30 to "CgkIuqy3oN8HEAIQBw", + 35 to "CgkIuqy3oN8HEAIQCA", + 40 to "CgkIuqy3oN8HEAIQCQ", + 45 to "CgkIuqy3oN8HEAIQCg", + 50 to "CgkIuqy3oN8HEAIQCw", + ) + attendu.forEach { (questions, id) -> + assertEquals(id, GameConfig(questions, 5, 12).leaderboardId()) + } + } + + @Test + fun `pas d'id de board hors liste des nombres de questions`() { + assertNull(GameConfig(7, 5, 12).leaderboardId()) + assertNull(GameConfig(60, 5, 12).leaderboardId()) } }