# 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
