510 lines
14 KiB
Markdown
510 lines
14 KiB
Markdown
# 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<T, E>
|
|
|
|
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<String, Exception> success = Result.success("Données chargées");
|
|
|
|
// Créer un résultat erreur
|
|
Result<String, Exception> 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<Questions, QuestionLoadException> result = repository.loadQuestions();
|
|
|
|
if (result.isSuccess()) {
|
|
Questions questions = result.getData();
|
|
List<Question> 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<Question, QuestionLoadException> 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 (`<J1>`, `<variante>`, 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<String> 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<String> 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<String> 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<Questions, QuestionRepository.QuestionLoadException> 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<Question, QuestionRepository.QuestionLoadException> 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<Data, Exception> 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("<J1>", 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<Questions>
|
|
│
|
|
├── processQuestion() ──────► GameEngine
|
|
│ │
|
|
│ ├── Remplacer <J1>, <J2>, etc.
|
|
│ ├── Remplacer <variante>
|
|
│ ├── Gérer <manches>
|
|
│ └── 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
|