# BoideloV3 - Documentation Architecture ## Vue d'ensemble Ce document décrit l'architecture mise en place pour l'application BoideloV3, un jeu de défi à boire pour Android. L'architecture suit le pattern **Repository + Separation of Concerns**, permettant : - Une meilleure séparation des responsabilités - Un code testable unitairement - Une gestion d'erreurs robuste - Une maintenance facilitée --- ## Structure du projet ``` app/src/main/java/com/example/boidelov3/ ├── data/ # Couche d'accès aux données │ ├── Result.java # Wrapper de résultat (succès/erreur) │ └── QuestionRepository.java # Repository pour les questions ├── game/ # Logique métier du jeu │ └── GameEngine.java # Moteur de jeu ├── ui/ # Activités et Fragments │ ├── Jeux.java # Activité principale du jeu │ ├── JeuxParametres.java # Écran de paramètres │ ├── EndGameActivity.java # Écran de fin de partie │ └── MainActivity.java # Écran d'accueil └── utils/ # Utilitaires ├── DatabaseConnection.java # Connexion BDD PostgreSQL ├── SoundManager.java # Gestion des sons └── BoideloAnimationUtils.java # Animations UI app/src/test/java/com/example/boidelov3/ ├── data/ │ └── ResultTest.java # Tests de Result └── game/ └── GameEngineTest.java # Tests de GameEngine ``` --- ## 1. Couche Data (données) ### 1.1 Result Wrapper type-safe pour représenter le résultat d'une opération qui peut échouer. **Avantages :** - Remplace les exceptions par des valeurs de retour - Force le traitement des erreurs - Rend le code plus prévisible **Utilisation :** ```java // Créer un résultat succès Result success = Result.success("Données chargées"); // Créer un résultat erreur Result failure = Result.failure(new Exception("Erreur de chargement")); // Utiliser le résultat if (result.isSuccess()) { String data = result.getData(); // Traiter les données } else { Exception error = result.getError(); // Gérer l'erreur } // Ou avec getOrElse String value = result.getOrElse("valeur par défaut"); ``` **Méthodes disponibles :** | Méthode | Description | |---------|-------------| | `success(T data)` | Crée un résultat réussi | | `failure(E error)` | Crée un résultat d'erreur | | `isSuccess()` | Vrai si réussi | | `isFailure()` | Vrai si échoué | | `getData()` | Retourne les données (null si erreur) | | `getError()` | Retourne l'erreur (null si succès) | | `getOrNull()` | Retourne les données ou null | | `getOrElse(T default)` | Retourne les données ou une valeur par défaut | --- ### 1.2 QuestionRepository Gère l'accès aux données des questions (JSON + SharedPreferences). **Responsabilités :** - Charger les questions depuis `assets/question.json` - Suivre les questions déjà posées - Sauvegarder les statistiques de jeu **Utilisation :** ```java // Créer le repository QuestionRepository repository = new QuestionRepository(context); // Charger les questions Result result = repository.loadQuestions(); if (result.isSuccess()) { Questions questions = result.getData(); List questionList = questions.getQuestions(); // Utiliser les questions } else { QuestionLoadException error = result.getError(); Log.e("TAG", "Erreur: " + error.getMessage()); } // Obtenir une question aléatoire non posée Result randomResult = repository.getRandomUnaskedQuestion(questionList); // Réinitialiser les questions posées repository.resetAskedQuestions(); // Sauvegarder les stats repository.saveGameStats(questionsPlayed, playersCount); ``` **Méthodes principales :** | Méthode | Description | |---------|-------------| | `loadQuestions()` | Charge toutes les questions depuis le JSON | | `getRandomUnaskedQuestion(List)` | Retourne une question non posée | | `markQuestionAsAsked(int)` | Marque une question comme posée | | `resetAskedQuestions()` | Réinitialise le suivi des questions | | `saveGameStats(int, int)` | Sauvegarde les statistiques | --- ## 2. Couche Game (logique métier) ### 2.1 GameEngine Contient toute la logique du jeu, indépendante de l'UI. **Responsabilités :** - Remplacement des variables dans les questions (``, ``, etc.) - Gestion des manches actives - Sélection aléatoire de joueurs - Calcul des gorgées **Utilisation :** ```java // Créer le moteur de jeu GameEngine engine = new GameEngine(); // Préparer les joueurs List players = Arrays.asList("Alice", "Bob", "Charlie", "David"); int addedGorgees = 2; // Paramètre de jeu // Traiter une question Question rawQuestion = ...; // Question depuis le JSON ProcessedQuestion processed = engine.processQuestion(rawQuestion, players, addedGorgees); // Afficher la question traitée textView.setText(Html.fromHtml(processed.question.getQuestion())); // Vérifier si c'est une manche if (processed.isManche) { // C'est un défi à manches Log.d("Game", "Manche active: " + processed.question.getManchesRestantes()); } // Mettre à jour les manches (à chaque tour) MancheState mancheState = engine.updateManches(); if (mancheState.endMessage != null) { // Une manche s'est terminée Toast.makeText(context, mancheState.endMessage, Toast.LENGTH_LONG).show(); } else if (mancheState.activeManche != null) { // Une manche est toujours active Question activeManche = mancheState.activeManche; int remaining = activeManche.getManchesRestantes(); // Afficher le compteur de manches } // Sélectionner des joueurs aléatoires List selectedPlayers = engine.selectRandomPlayers(players, 3); // Retourne 3 joueurs uniques // Vider toutes les manches (fin de partie) engine.clearManches(); ``` **Classes internes :** | Classe | Description | |--------|-------------| | `ProcessedQuestion` | Conteneur pour une question traitée | | `MancheState` | État des manches après mise à jour | **Méthodes principales :** | Méthode | Description | |---------|-------------| | `processQuestion(Question, List, int)` | Traite une question (remplace variables) | | `selectRandomPlayers(List, int)` | Sélectionne n joueurs uniques | | `updateManches()` | Met à jour et retourne l'état des manches | | `hasActiveManche()` | Vrai si une manche est active | | `clearManches()` | Vide toutes les manches actives | --- ## 3. Utilisation recommandée dans une Activity Voici comment intégrer les nouvelles classes dans `Jeux.java` : ```java public class Jeux extends AppCompatActivity { private QuestionRepository repository; private GameEngine gameEngine; private List players; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_jeux); // Initialiser les composants repository = new QuestionRepository(this); gameEngine = new GameEngine(); players = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR"); // Charger les questions loadAndStartGame(); } private void loadAndStartGame() { Result result = repository.loadQuestions(); if (result.isSuccess()) { Questions questions = result.getData(); displayNextQuestion(questions); } else { Toast.makeText(this, "Erreur de chargement", Toast.LENGTH_SHORT).show(); finish(); } } private void displayNextQuestion(Questions allQuestions) { // Obtenir une question aléatoire Result result = repository.getRandomUnaskedQuestion(allQuestions.getQuestions()); if (result.isFailure()) { endGame(); return; } Question question = result.getData(); // Traiter la question avec le moteur de jeu int addedGorgees = getIntent().getIntExtra("EXTRA_AJOUT_GORGEE", 0); GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, addedGorgees); // Afficher questionTextView.setText(Html.fromHtml(processed.question.getQuestion())); // Gérer les manches GameEngine.MancheState mancheState = gameEngine.updateManches(); if (mancheState.endMessage != null) { Toast.makeText(this, mancheState.endMessage, Toast.LENGTH_LONG).show(); } } private void endGame() { repository.saveGameStats(totalQuestions, players.size()); // Naviguer vers EndGameActivity } @Override protected void onDestroy() { super.onDestroy(); gameEngine.clearManches(); } } ``` --- ## 4. Tests unitaires Les tests sont situés dans `app/src/test/java/`. ### 4.1 Exécuter les tests **Depuis Android Studio :** 1. Clic droit sur la classe de test 2. Select "Run 'ResultTest'" **Depuis la ligne de commande :** ```bash ./gradlew testDebugUnitTest ``` *Note : Avec Gradle 8.13, il y a un bug connu. Utilisez Android Studio ou un JDK 17-21.* ### 4.2 Résultat des tests | Classe | Tests | Couverture | |--------|-------|------------| | `ResultTest` | 8 tests | 100% Result | | `GameEngineTest` | 15 tests | Logique jeu complète | --- ## 5. Connexion Base de Données ### 5.1 DatabaseConnection Classe moderne pour la connexion PostgreSQL (remplace l'obsolète AsyncTask). **Utilisation :** ```java DatabaseConnection dbConnection = new DatabaseConnection(); dbConnection.connectAsync(new DatabaseConnection.ConnectionCallback() { @Override public void onSuccess(PGConnection connection) { // Connexion réussie - thread principal (UI) Log.d("Database", "Connecté !"); // Utiliser la connexion... } @Override public void onFailure(Exception error) { // Erreur - thread principal (UI) Log.e("Database", "Erreur de connexion", error); Toast.makeText(context, "Erreur: " + error.getMessage(), Toast.LENGTH_SHORT).show(); } }); // Dans onDestroy() @Override protected void onDestroy() { super.onDestroy(); dbConnection.shutdown(); // Libérer les ressources } ``` ### 5.2 Configuration Les identifiants de connexion sont dans `local.properties` (non versionné) : ```properties # Database credentials (DO NOT COMMIT) db.url=jdbc:postgresql://your-host:5432/database db.user=your-username db.password=your-password ``` Ils sont injectés automatiquement dans `BuildConfig` par `build.gradle`. --- ## 6. Bonnes pratiques ### 6.1 Gestion des erreurs ```java // ✅ BON - Utiliser Result Result result = repository.loadQuestions(); if (result.isSuccess()) { // Traiter les données } else { // Gérer l'erreur } // ❌ Mauvais - Exceptions non gérées try { Data data = repository.loadQuestions(); } catch (Exception e) { // Peut être oublié... } ``` ### 6.2 Séparation des responsabilités ```java // ✅ BON - Logique dans GameEngine ProcessedQuestion processed = gameEngine.processQuestion(question, players, addedGorgees); // ❌ Mauvais - Logique dans l'Activity String text = question.getQuestion().replace("", players.get(0)); // ... logique compliquée dans l'UI ``` ### 6.3 Cycle de vie ```java @Override protected void onDestroy() { super.onDestroy(); // Toujours nettoyer les ressources if (dbConnection != null) { dbConnection.shutdown(); } if (gameEngine != null) { gameEngine.clearManches(); } } ``` --- ## 7. Diagramme de flux ``` ┌─────────────────┐ │ Jeux Activity │ (UI) └────────┬────────┘ │ ├── loadQuestions() ──────► QuestionRepository │ │ │ ├── Lire JSON │ └── Retourner Result │ ├── processQuestion() ──────► GameEngine │ │ │ ├── Remplacer , , etc. │ ├── Remplacer │ ├── Gérer │ └── Retourner ProcessedQuestion │ └── Afficher dans TextView ``` --- ## 8. Migration depuis l'ancien code ### Ancienne méthode (AsyncTask) ```java // Ancien code - À NE PAS UTILISER new DatabaseConnection().execute(); @Override protected void onPostExecute(Connection connection) { if (connection != null) { // ... } } ``` ### Nouvelle méthode (ExecutorService + Callback) ```java // Nouveau code - Recommandé DatabaseConnection db = new DatabaseConnection(); db.connectAsync(new ConnectionCallback() { @Override public void onSuccess(PGConnection connection) { // Succès } @Override public void onFailure(Exception error) { // Erreur } }); ``` --- ## 9. Dépannage ### Problème : Les tests unitaires échouent avec Gradle 8.13 **Solution :** Exécutez les tests depuis Android Studio, ou utilisez un JDK 17-21 au lieu de Java 24. ### Problème : "Cannot resolve BuildConfig" **Solution :** Faites un Build > Rebuild Project pour régénérer BuildConfig. ### Problème : "QuestionRepository.QuestionLoadException" **Solution :** Importez la classe interne : ```java import com.example.boidelov3.data.QuestionRepository.QuestionLoadException; ``` --- ## 10. Ressources supplémentaires - [Android Guide to App Architecture](https://developer.android.com/topic/architecture) - [Repository Pattern](https://developer.android.com/topic/architecture/data-layer) - [Result Type in Java](https://www.baeldung.com/java-result-type) --- **Dernière mise à jour :** 31 décembre 2025 **Version :** 3.0