125 lines
6.3 KiB
Markdown
125 lines
6.3 KiB
Markdown
# 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
|
||
```
|
||
|
||
```kotlin
|
||
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
|
||
que `filesDirPath`. `HomeScreen` gagne `onLeaderboard: () -> Unit` ; `ResultsScreen`
|
||
affiche les points soumis.
|
||
- `androidApp` : `PlayGamesServices(activity)` implémente l'interface avec
|
||
`play-services-games-v2:22.0.0` (dépendance ajoutée au catalogue Gradle) :
|
||
- `signIn()` / `isAuthenticated()` via `GamesSignInClient` ;
|
||
- `submitScoreImmediate(leaderboardId, score)` résultat attendu, erreur → `false` ;
|
||
- `getLeaderboardIntent(leaderboardId)` lancé via l'Activity Result API
|
||
(`registerForActivityResult` dans `AppActivity`) ;
|
||
- appels GMS effectués en `Dispatchers.IO` via `Tasks.await`.
|
||
- desktop/iOS : `NoopGamesServices` (toutes méthodes no-op, `signIn` → `false`).
|
||
|
||
## 5. Flux utilisateur
|
||
|
||
1. Lancement de l'app : `App()` déclenche `gamesServices.signIn()` dans un effet de
|
||
lancement (no-op sur desktop/iOS). Connexion silencieuse, non bloquante, sans UI.
|
||
Échec → l'app fonctionne normalement hors ligne.
|
||
2. Fin de partie : si connecté, envoi immédiat du score au board `questions_N`.
|
||
Échec réseau/connexion → silencieux (l'app n'attend pas).
|
||
3. Accueil : bouton « Classement » sous « Historique ». Si non connecté, tente la
|
||
connexion puis ouvre l'UI native Play Jeux sur le board du `questionCount`
|
||
sé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.
|
||
4. Écran des résultats : ligne « Points classement : X » sous le temps moyen.
|
||
|
||
## 6. Faisabilité
|
||
|
||
- `play-services-games-v2` 22.0.0 (juillet 2026) : sign-in v2 implicite, client
|
||
leaderboards, intents d'affichage ; **minSdk 24 requis** (le projet passe de
|
||
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.
|
||
|
||
## 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 »).
|
||
4. 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) et `LeaderboardsTest` (mapping `GameConfig` → `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 `GamesServices` permettra 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é)
|