6.2 KiB
Design : Play Games Services (Android) — Livrets
Date : 2026-08-28 Statut : Validé par le produit
1. Objectif
Sur Android uniquement : authentification via Play Jeux Services v2 et classements en ligne. Un leaderboard par nombre de questions (5, 10, …, 50), avec un score pondéré par la difficulté (valeur des produits réussis et temps par question). Le jeu reste pleinement fonctionnel hors connexion et sans compte.
2. Décisions clés
| Décision | Choix |
|---|---|
| Découpage des boards | 10 leaderboards, un par valeur de questionCount (5 → 50, pas de 5). Les combinaisons complètes (3360) dépassent la limite de 70 boards Play Jeux |
| Score classé | Points pondérés : round((Σ produits réussis) × 30 ÷ secondsPerQuestion) |
| Authentification | Connexion silencieuse auto au lancement (PGS v2, non bloquante, sans UI) |
| Envoi du score | Automatique en fin de partie si connecté et ≥ 1 bonne réponse, échec silencieux |
| Consultation | Bouton « Classement » sur l'accueil → UI native Play Jeux du board correspondant au questionCount sélectionné |
| Intégration KMP | Interface GamesServices dans commonMain, injectée dans App() ; implémentation Android dans androidApp ; no-op sur desktop/iOS |
| Transparence | Ligne « Points classement : X » sur l'écran des résultats |
Rejeté : un board par combinaison (questionCount × temps × table = 3360 > 70 boards max) ; boards par table ou par temps (score plus difficile à rendre équitable) ; bons réponses bruts ou taux de réussite (égalités massives à N ou 100 %) ; expect/actual dans sharedUI (ajouterait la dépendance GMS au module partagé) ; UI classement in-app (coût disproportionné).
3. Formule du score
score = round( (Σ produits des questions réussies) × 30 ÷ secondsPerQuestion )
- Chaque bonne réponse rapporte la valeur de son produit (
a × b) : les grandes tables valent plus de points. - Le facteur
(30 ÷ secondsPerQuestion)valorise les réglages rapides (3 s/question → ×10 ; 30 s/question → ×1). - Exemple : 10 questions, table 12, 5 s, 10/10 → ~2500 points.
- 0 bonne réponse → pas de soumission (score 0 non soumis).
Fonction pure dans commonMain : fun GameResult.leaderboardScore(): Long, testable sans GMS.
4. Architecture
sharedUI/commonMain/kotlin/com/livrets/games/
GamesServices.kt interface GamesServices + object NoopGamesServices
Leaderboards.kt fun GameConfig.leaderboardId(): String // "questions_N"
LeaderboardScore.kt fun GameResult.leaderboardScore(): Long
interface GamesServices {
suspend fun signIn(): Boolean // true si connecté à la fin
suspend fun isSignedIn(): Boolean
suspend fun submitScore(leaderboardId: String, score: Long): Boolean
suspend fun showLeaderboard(leaderboardId: String)
}
App(filesDirPath, gamesServices: GamesServices = NoopGamesServices, …)— même pattern quefilesDirPath.HomeScreengagneonLeaderboard: () -> Unit;ResultsScreenaffiche les points soumis.androidApp:PlayGamesServices(activity)implémente l'interface avecplay-services-games-v2:22.0.0(dépendance ajoutée au catalogue Gradle) :signIn()/isAuthenticated()viaGamesSignInClient;submitScoreImmediate(leaderboardId, score)résultat attendu, erreur →false;getLeaderboardIntent(leaderboardId)lancé via l'Activity Result API (registerForActivityResultdansAppActivity) ;- appels GMS effectués en
Dispatchers.IOviaTasks.await.
- desktop/iOS :
NoopGamesServices(toutes méthodes no-op,signIn→false).
5. Flux utilisateur
- Lancement de l'app :
App()déclenchegamesServices.signIn()dans un effet de lancement (no-op sur desktop/iOS). Connexion silencieuse, non bloquante, sans UI. Échec → l'app fonctionne normalement hors ligne. - Fin de partie : si connecté, envoi immédiat du score au board
questions_N. Échec réseau/connexion → silencieux (l'app n'attend pas). - Accueil : bouton « Classement » sous « Historique ». Si non connecté, tente la
connexion puis ouvre l'UI native Play Jeux sur le board du
questionCountsélectionné sur l'accueil. Si la connexion échoue ou que l'ouverture de l'UI échoue (Play Jeux absent, pas de compte), rien ne s'ouvre — pas d'erreur bloquante. - Écran des résultats : ligne « Points classement : X » sous le temps moyen.
6. Faisabilité
play-services-games-v222.0.0 (juillet 2026) : sign-in v2 implicite, client leaderboards, intents d'affichage ; minSdk 23 compatible.- 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.
7. Prérequis Play Console (manuel, hors code)
- Créer l'app dans Play Console (package
com.livrets.androidApp). - Jouer → Play Jeux Services : créer le jeu, lier l'app Android, ajouter les empreintes SHA-1 debug et release.
- Créer les 10 leaderboards avec IDs
questions_5…questions_50(« 5 questions » … « 50 questions »). - Ajouter des testeurs PGS pour valider la connexion en développement.
Sans ce setup, toutes les opérations échouent silencieusement : aucune régression pour l'utilisateur, l'app reste jouable.
8. Tests & vérifications
commonTest:LeaderboardScoreTest(0 réussite → pas de score ; 100 % ; facteur vitesse ; arrondi) etLeaderboardsTest(mappingGameConfig→questions_N).- Vérification manuelle Android (appareil avec compte Play + console configurée) : connexion, envoi visible dans l'UI Play Jeux, ouverture du bon board.
- Régression desktop : le no-op ne casse rien (lancement, partie, résultats).
- Régression :
./gradlew :sharedUI:jvmTest+./gradlew build(3 cibles).
9. Hors périmètre
- iOS Game Center (l'interface
GamesServicespermettra d'injecter un équivalent plus tard) - Achievements, sauvegarde cloud, profil joueur, UI classement in-app
- Déconnexion in-app (gérée par l'app Play Jeux, standard PGS v2)
- Historisation locale des scores soumis (l'historique existant reste inchangé)