modernisation api et test unitaire
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"Bash(tree:*)",
|
"Bash(tree:*)",
|
||||||
"Bash(./gradlew:*)",
|
"Bash(./gradlew:*)",
|
||||||
"Bash(dir:*)"
|
"Bash(dir:*)",
|
||||||
|
"Bash(timeout:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
# 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
|
||||||
@@ -1,41 +1,77 @@
|
|||||||
package com.example.boidelov3;
|
package com.example.boidelov3;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.Handler;
|
||||||
import com.example.boidelov3.BuildConfig;
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import com.example.boidelov3.BuildConfig;
|
||||||
import com.impossibl.postgres.api.jdbc.PGConnection;
|
import com.impossibl.postgres.api.jdbc.PGConnection;
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère la connexion à la base de données PostgreSQL de manière asynchrone.
|
||||||
|
* Remplace l'obsolète AsyncTask par ExecutorService + Handler.
|
||||||
|
*/
|
||||||
|
public class DatabaseConnection {
|
||||||
|
|
||||||
public class DatabaseConnection extends AsyncTask<Void, Void, PGConnection> {
|
|
||||||
private static final String DB_URL = BuildConfig.DB_URL;
|
private static final String DB_URL = BuildConfig.DB_URL;
|
||||||
private static final String USER = BuildConfig.DB_USER;
|
private static final String USER = BuildConfig.DB_USER;
|
||||||
private static final String PASSWORD = BuildConfig.DB_PASSWORD;
|
private static final String PASSWORD = BuildConfig.DB_PASSWORD;
|
||||||
|
|
||||||
@Override
|
private final ExecutorService executorService;
|
||||||
protected PGConnection doInBackground(Void... params) {
|
private final Handler mainHandler;
|
||||||
PGConnection connection = null;
|
|
||||||
try {
|
public DatabaseConnection() {
|
||||||
// Code de connexion à la base de données PostgreSQL
|
this.executorService = Executors.newSingleThreadExecutor();
|
||||||
String url = DB_URL;
|
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||||
String username = USER;
|
|
||||||
String password = PASSWORD;
|
|
||||||
connection = (PGConnection) DriverManager.getConnection(url, username, password);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
protected void onPostExecute(Connection connection) {
|
* Interface de callback pour le résultat de la connexion.
|
||||||
// Traitez le résultat de la connexion ici
|
*/
|
||||||
if (connection != null) {
|
public interface ConnectionCallback {
|
||||||
// Connexion réussie
|
void onSuccess(PGConnection connection);
|
||||||
} else {
|
void onFailure(Exception error);
|
||||||
// Échec de la connexion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Connexion asynchrone à la base de données.
|
||||||
|
*
|
||||||
|
* @param callback Callback appelé sur le thread principal
|
||||||
|
*/
|
||||||
|
public void connectAsync(ConnectionCallback callback) {
|
||||||
|
executorService.execute(() -> {
|
||||||
|
PGConnection connection = null;
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = (PGConnection) DriverManager.getConnection(DB_URL, USER, PASSWORD);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retour sur le thread principal pour notifier le résultat
|
||||||
|
final PGConnection finalConnection = connection;
|
||||||
|
final Exception finalError = error;
|
||||||
|
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
if (finalError == null && finalConnection != null) {
|
||||||
|
callback.onSuccess(finalConnection);
|
||||||
|
} else {
|
||||||
|
callback.onFailure(finalError != null ? finalError : new SQLException("Connection failed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ferme l'executorService pour libérer les ressources.
|
||||||
|
* À appeler dans onDestroy() de l'Activity.
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,7 +54,26 @@ public class Jeuxold extends AppCompatActivity {
|
|||||||
|
|
||||||
|
|
||||||
//Parti OpenAI ; keyOpenai ; ratiOpenai, openAI
|
//Parti OpenAI ; keyOpenai ; ratiOpenai, openAI
|
||||||
//new DatabaseConnection().execute();
|
// Ancienne AsyncTask (obsolète) :
|
||||||
|
// new DatabaseConnection().execute();
|
||||||
|
|
||||||
|
// Nouvelle API avec callback (recommandé) :
|
||||||
|
// DatabaseConnection dbConnection = new DatabaseConnection();
|
||||||
|
// dbConnection.connectAsync(new DatabaseConnection.ConnectionCallback() {
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess(PGConnection connection) {
|
||||||
|
// // Connexion réussie - utiliser la connexion ici
|
||||||
|
// Log.d("Database", "Connected successfully");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(Exception error) {
|
||||||
|
// // Erreur de connexion
|
||||||
|
// Log.e("Database", "Connection failed", error);
|
||||||
|
// Toast.makeText(Jeuxold.this, "Erreur de connexion", Toast.LENGTH_SHORT).show();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// N'oubliez pas d'appeler dbConnection.shutdown() dans onDestroy()
|
||||||
|
|
||||||
// if(openAI) {
|
// if(openAI) {
|
||||||
// ChatGPTTask chatGPTTask = new ChatGPTTask( this, keyOpenai);
|
// ChatGPTTask chatGPTTask = new ChatGPTTask( this, keyOpenai);
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package com.example.boidelov3.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.example.boidelov3.Question;
|
||||||
|
import com.example.boidelov3.Questions;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository pour gérer l'accès aux données des questions.
|
||||||
|
* Sépare la logique de chargement et de stockage de l'UI.
|
||||||
|
*/
|
||||||
|
public class QuestionRepository {
|
||||||
|
private static final String TAG = "QuestionRepository";
|
||||||
|
private static final String PREFS_NAME = "app";
|
||||||
|
private static final String KEY_ASKED_QUESTIONS = "askedQuestions";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
private final Gson gson;
|
||||||
|
private Questions cachedQuestions;
|
||||||
|
|
||||||
|
public QuestionRepository(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
this.gson = new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge toutes les questions depuis le fichier JSON.
|
||||||
|
*
|
||||||
|
* @return Result contenant les questions ou une erreur
|
||||||
|
*/
|
||||||
|
public Result<Questions, QuestionLoadException> loadQuestions() {
|
||||||
|
if (cachedQuestions != null) {
|
||||||
|
return Result.success(cachedQuestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream is = context.getAssets().open("question.json");
|
||||||
|
byte[] buffer = new byte[is.available()];
|
||||||
|
is.read(buffer);
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
String json = new String(buffer, "UTF-8");
|
||||||
|
cachedQuestions = gson.fromJson(json, Questions.class);
|
||||||
|
|
||||||
|
if (cachedQuestions == null || cachedQuestions.getQuestions() == null) {
|
||||||
|
return Result.failure(new QuestionLoadException("Impossible de parser le JSON"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Chargé " + cachedQuestions.getQuestions().size() + " questions");
|
||||||
|
return Result.success(cachedQuestions);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Erreur de chargement des questions", e);
|
||||||
|
return Result.failure(new QuestionLoadException("Erreur de lecture du fichier questions.json", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient une question aléatoire qui n'a pas encore été posée.
|
||||||
|
*
|
||||||
|
* @param questions La liste complète des questions
|
||||||
|
* @return Une question ou null si toutes ont été posées
|
||||||
|
*/
|
||||||
|
public Result<Question, QuestionLoadException> getRandomUnaskedQuestion(List<Question> questions) {
|
||||||
|
Set<String> askedQuestions = prefs.getStringSet(KEY_ASKED_QUESTIONS, new HashSet<>());
|
||||||
|
|
||||||
|
List<Question> unaskedQuestions = new ArrayList<>();
|
||||||
|
for (Question question : questions) {
|
||||||
|
if (!askedQuestions.contains(String.valueOf(question.getId()))) {
|
||||||
|
unaskedQuestions.add(question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unaskedQuestions.isEmpty()) {
|
||||||
|
return Result.failure(new QuestionLoadException("Toutes les questions ont été posées"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sélection aléatoire
|
||||||
|
java.util.Random random = new java.util.Random();
|
||||||
|
Question selected = unaskedQuestions.get(random.nextInt(unaskedQuestions.size()));
|
||||||
|
|
||||||
|
// Marquer comme posée
|
||||||
|
askedQuestions.add(String.valueOf(selected.getId()));
|
||||||
|
prefs.edit().putStringSet(KEY_ASKED_QUESTIONS, askedQuestions).apply();
|
||||||
|
|
||||||
|
return Result.success(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marque une question comme étant posée.
|
||||||
|
*/
|
||||||
|
public void markQuestionAsAsked(int questionId) {
|
||||||
|
Set<String> askedQuestions = prefs.getStringSet(KEY_ASKED_QUESTIONS, new HashSet<>());
|
||||||
|
askedQuestions.add(String.valueOf(questionId));
|
||||||
|
prefs.edit().putStringSet(KEY_ASKED_QUESTIONS, askedQuestions).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Réinitialise la liste des questions posées.
|
||||||
|
*/
|
||||||
|
public void resetAskedQuestions() {
|
||||||
|
prefs.edit().remove(KEY_ASKED_QUESTIONS).apply();
|
||||||
|
Log.d(TAG, "Questions posées réinitialisées");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sauvegarde les statistiques d'une partie.
|
||||||
|
*/
|
||||||
|
public void saveGameStats(int questionsPlayed, int playersCount) {
|
||||||
|
SharedPreferences statsPrefs = context.getSharedPreferences("game_stats", Context.MODE_PRIVATE);
|
||||||
|
statsPrefs.edit()
|
||||||
|
.putInt("questions_played", questionsPlayed)
|
||||||
|
.putInt("players_count", playersCount)
|
||||||
|
.apply();
|
||||||
|
Log.d(TAG, "Stats sauvegardées: " + questionsPlayed + " questions, " + playersCount + " joueurs");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception personnalisée pour les erreurs de chargement.
|
||||||
|
*/
|
||||||
|
public static class QuestionLoadException extends Exception {
|
||||||
|
public QuestionLoadException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuestionLoadException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.example.boidelov3.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper pour représenter le résultat d'une opération qui peut échouer.
|
||||||
|
* Remplace les exceptions par des valeurs de retour typées.
|
||||||
|
*
|
||||||
|
* @param <T> Le type de donnée en cas de succès
|
||||||
|
* @param <E> Le type d'erreur en cas d'échec
|
||||||
|
*/
|
||||||
|
public class Result<T, E extends Exception> {
|
||||||
|
private final T data;
|
||||||
|
private final E error;
|
||||||
|
|
||||||
|
private Result(T data, E error) {
|
||||||
|
this.data = data;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un résultat réussi
|
||||||
|
*/
|
||||||
|
public static <T, E extends Exception> Result<T, E> success(T data) {
|
||||||
|
return new Result<>(data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un résultat d'erreur
|
||||||
|
*/
|
||||||
|
public static <T, E extends Exception> Result<T, E> failure(E error) {
|
||||||
|
return new Result<>(null, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true si l'opération a réussi
|
||||||
|
*/
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return error == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true si l'opération a échoué
|
||||||
|
*/
|
||||||
|
public boolean isFailure() {
|
||||||
|
return error != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return les données en cas de succès, null sinon
|
||||||
|
*/
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return l'erreur en cas d'échec, null sinon
|
||||||
|
*/
|
||||||
|
public E getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return les données ou null si échec
|
||||||
|
*/
|
||||||
|
public T getOrNull() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return les données ou une valeur par défaut
|
||||||
|
*/
|
||||||
|
public T getOrElse(T defaultValue) {
|
||||||
|
return isSuccess() ? data : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package com.example.boidelov3.game;
|
||||||
|
|
||||||
|
import com.example.boidelov3.Question;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moteur de jeu contenant toute la logique métier.
|
||||||
|
* Indépendant de l'UI et du framework Android.
|
||||||
|
*/
|
||||||
|
public class GameEngine {
|
||||||
|
private final Random random;
|
||||||
|
private final List<Question> activeManches;
|
||||||
|
|
||||||
|
public GameEngine() {
|
||||||
|
this.random = new Random();
|
||||||
|
this.activeManches = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traite une question en remplaçant les variables.
|
||||||
|
*
|
||||||
|
* @param question La question à traiter
|
||||||
|
* @param players La liste des joueurs
|
||||||
|
* @param addedGorgees Nombre de gorgées additionnelles à ajouter
|
||||||
|
* @return La question traitée avec les variables remplacées
|
||||||
|
*/
|
||||||
|
public ProcessedQuestion processQuestion(Question question, List<String> players, int addedGorgees) {
|
||||||
|
String questionText = question.getQuestion();
|
||||||
|
boolean isManche = false;
|
||||||
|
|
||||||
|
// Remplacer les variantes
|
||||||
|
if (question.getVariante() != null && !question.getVariante().isEmpty()) {
|
||||||
|
String chosenVariante = question.getVariante().get(random.nextInt(question.getVariante().size()));
|
||||||
|
questionText = questionText.replace("<variante>", chosenVariante);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer les manches
|
||||||
|
if (questionText.contains("<manches>")) {
|
||||||
|
int manchesCount = random.nextInt(10) + 5;
|
||||||
|
questionText = questionText.replace("<manches>", String.valueOf(manchesCount));
|
||||||
|
|
||||||
|
// Créer une copie de la question pour la manche active
|
||||||
|
Question mancheQuestion = copyQuestion(question);
|
||||||
|
mancheQuestion.setQuestion(questionText); // Question avec le nombre de manches
|
||||||
|
mancheQuestion.setManchesRestantes(manchesCount);
|
||||||
|
|
||||||
|
// Message de fin de manche
|
||||||
|
String stopMessage = question.getArret() != null ? question.getArret() : "Fin du défi!";
|
||||||
|
mancheQuestion.setArretMessageManche("Fin de défi!\n" + stopMessage);
|
||||||
|
|
||||||
|
activeManches.add(mancheQuestion);
|
||||||
|
isManche = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remplacer les joueurs
|
||||||
|
questionText = replacePlayerPlaceholders(questionText, players);
|
||||||
|
|
||||||
|
// Ajouter les gorgées
|
||||||
|
questionText = addGorgeesText(question, questionText, addedGorgees);
|
||||||
|
|
||||||
|
// Mettre à jour la question avec le texte traité
|
||||||
|
Question resultQuestion = isManche ? activeManches.get(activeManches.size() - 1) : copyQuestion(question);
|
||||||
|
resultQuestion.setQuestion(questionText);
|
||||||
|
|
||||||
|
return new ProcessedQuestion(resultQuestion, isManche);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remplace les placeholders de joueurs dans la question.
|
||||||
|
*/
|
||||||
|
private String replacePlayerPlaceholders(String questionText, List<String> players) {
|
||||||
|
boolean hasJ1 = questionText.contains("<J1>");
|
||||||
|
boolean hasJ2 = questionText.contains("<J2>");
|
||||||
|
boolean hasJ3 = questionText.contains("<J3>");
|
||||||
|
|
||||||
|
if (!hasJ1 && !hasJ2 && !hasJ3) {
|
||||||
|
return questionText;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> selectedPlayers = selectRandomPlayers(players, 3);
|
||||||
|
String result = questionText;
|
||||||
|
|
||||||
|
if (hasJ1 && hasJ2 && hasJ3 && selectedPlayers.size() >= 3) {
|
||||||
|
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||||
|
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||||
|
result = result.replace("<J3>", selectedPlayers.get(2));
|
||||||
|
} else if (hasJ1 && hasJ2 && selectedPlayers.size() >= 2) {
|
||||||
|
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||||
|
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||||
|
} else if (hasJ1 && selectedPlayers.size() >= 1) {
|
||||||
|
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute le texte des gorgées à la question.
|
||||||
|
*/
|
||||||
|
private String addGorgeesText(Question question, String questionText, int addedGorgees) {
|
||||||
|
if (!question.isDistribution() && !question.isRecois()) {
|
||||||
|
return questionText;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(questionText);
|
||||||
|
sb.append(" ");
|
||||||
|
|
||||||
|
int totalGorgees = question.getGorger() + addedGorgees;
|
||||||
|
|
||||||
|
// Déterminer si boire ou distribuer
|
||||||
|
if (question.isRecois() && question.isDistribution()) {
|
||||||
|
sb.append(random.nextBoolean() ? "<b>bois</b>" : "<b>distribue</b>");
|
||||||
|
} else if (question.isRecois()) {
|
||||||
|
sb.append("<b>bois</b>");
|
||||||
|
} else {
|
||||||
|
sb.append("<b>distribue</b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" ").append(totalGorgees).append(" gorgée");
|
||||||
|
if (totalGorgees > 1) {
|
||||||
|
sb.append("s");
|
||||||
|
}
|
||||||
|
sb.append(".");
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sélectionne n joueurs aléatoires uniques.
|
||||||
|
*/
|
||||||
|
public List<String> selectRandomPlayers(List<String> allPlayers, int count) {
|
||||||
|
Set<String> selected = new HashSet<>();
|
||||||
|
int available = Math.min(count, allPlayers.size());
|
||||||
|
|
||||||
|
while (selected.size() < available) {
|
||||||
|
selected.add(allPlayers.get(random.nextInt(allPlayers.size())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour les manches actives et retourne l'état.
|
||||||
|
*
|
||||||
|
* @return MancheState contenant la manche à afficher ou null si terminée
|
||||||
|
*/
|
||||||
|
public MancheState updateManches() {
|
||||||
|
if (activeManches.isEmpty()) {
|
||||||
|
return new MancheState(null, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Question mancheQuestion = activeManches.get(0);
|
||||||
|
mancheQuestion.setManchesRestantes(mancheQuestion.getManchesRestantes() - 1);
|
||||||
|
|
||||||
|
if (mancheQuestion.getManchesRestantes() <= 0) {
|
||||||
|
// Manche terminée
|
||||||
|
String endMessage = mancheQuestion.getArretMessageManche();
|
||||||
|
activeManches.remove(0);
|
||||||
|
return new MancheState(null, false, endMessage);
|
||||||
|
} else {
|
||||||
|
// Manche toujours active
|
||||||
|
return new MancheState(mancheQuestion, true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true si une manche est active
|
||||||
|
*/
|
||||||
|
public boolean hasActiveManche() {
|
||||||
|
return !activeManches.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return le nombre de manches actives
|
||||||
|
*/
|
||||||
|
public int getActiveManchesCount() {
|
||||||
|
return activeManches.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vide toutes les manches actives.
|
||||||
|
*/
|
||||||
|
public void clearManches() {
|
||||||
|
activeManches.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une copie d'une question (pour éviter de modifier l'original).
|
||||||
|
*/
|
||||||
|
private Question copyQuestion(Question source) {
|
||||||
|
Question copy = new Question();
|
||||||
|
copy.setId(source.getId());
|
||||||
|
copy.setQuestion(source.getQuestion());
|
||||||
|
copy.setArret(source.getArret());
|
||||||
|
copy.setVariante(source.getVariante());
|
||||||
|
copy.setDistribution(source.isDistribution());
|
||||||
|
copy.setRecois(source.isRecois());
|
||||||
|
copy.setGorger(source.getGorger());
|
||||||
|
copy.setManchesRestantes(source.getManchesRestantes());
|
||||||
|
copy.setArretMessageManche(source.getArretMessageManche());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conteneur pour une question traitée.
|
||||||
|
*/
|
||||||
|
public static class ProcessedQuestion {
|
||||||
|
public final Question question;
|
||||||
|
public final boolean isManche;
|
||||||
|
|
||||||
|
public ProcessedQuestion(Question question, boolean isManche) {
|
||||||
|
this.question = question;
|
||||||
|
this.isManche = isManche;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* État des manches après mise à jour.
|
||||||
|
*/
|
||||||
|
public static class MancheState {
|
||||||
|
public final Question activeManche;
|
||||||
|
public final boolean hasManche;
|
||||||
|
public final String endMessage;
|
||||||
|
|
||||||
|
public MancheState(Question activeManche, boolean hasManche, String endMessage) {
|
||||||
|
this.activeManche = activeManche;
|
||||||
|
this.hasManche = hasManche;
|
||||||
|
this.endMessage = endMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.example.boidelov3.data;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests unitaires pour la classe Result.
|
||||||
|
*/
|
||||||
|
public class ResultTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccess_createsSuccessfulResult() {
|
||||||
|
Result<String, Exception> result = Result.success("test data");
|
||||||
|
|
||||||
|
assertTrue("Result should be success", result.isSuccess());
|
||||||
|
assertFalse("Result should not be failure", result.isFailure());
|
||||||
|
assertEquals("Data should match", "test data", result.getData());
|
||||||
|
assertNull("Error should be null", result.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_createsFailedResult() {
|
||||||
|
Exception error = new Exception("test error");
|
||||||
|
Result<String, Exception> result = Result.failure(error);
|
||||||
|
|
||||||
|
assertFalse("Result should not be success", result.isSuccess());
|
||||||
|
assertTrue("Result should be failure", result.isFailure());
|
||||||
|
assertNull("Data should be null", result.getData());
|
||||||
|
assertEquals("Error should match", error, result.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOrNull_returnsDataWhenSuccess() {
|
||||||
|
Result<String, Exception> result = Result.success("test data");
|
||||||
|
|
||||||
|
assertEquals("Should return data", "test data", result.getOrNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOrNull_returnsNullWhenFailure() {
|
||||||
|
Result<String, Exception> result = Result.failure(new Exception("error"));
|
||||||
|
|
||||||
|
assertNull("Should return null", result.getOrNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOrElse_returnsDataWhenSuccess() {
|
||||||
|
Result<String, Exception> result = Result.success("test data");
|
||||||
|
|
||||||
|
assertEquals("Should return data", "test data", result.getOrElse("default"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOrElse_returnsDefaultWhenFailure() {
|
||||||
|
Result<String, Exception> result = Result.failure(new Exception("error"));
|
||||||
|
|
||||||
|
assertEquals("Should return default", "default", result.getOrElse("default"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithIntegerType() {
|
||||||
|
Result<Integer, Exception> result = Result.success(42);
|
||||||
|
|
||||||
|
assertTrue("Should be success", result.isSuccess());
|
||||||
|
assertEquals(Integer.valueOf(42), result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithCustomException() {
|
||||||
|
class CustomException extends Exception {
|
||||||
|
public CustomException(String message) { super(message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<String, CustomException> result = Result.failure(new CustomException("custom error"));
|
||||||
|
|
||||||
|
assertTrue("Should be failure", result.isFailure());
|
||||||
|
assertEquals("custom error", result.getError().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package com.example.boidelov3.game;
|
||||||
|
|
||||||
|
import com.example.boidelov3.Question;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests unitaires pour la classe GameEngine.
|
||||||
|
*/
|
||||||
|
public class GameEngineTest {
|
||||||
|
|
||||||
|
private GameEngine gameEngine;
|
||||||
|
private List<String> players;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
gameEngine = new GameEngine();
|
||||||
|
players = Arrays.asList("Alice", "Bob", "Charlie", "David");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectRandomPlayers_returnsCorrectCount() {
|
||||||
|
List<String> selected = gameEngine.selectRandomPlayers(players, 2);
|
||||||
|
|
||||||
|
assertEquals("Should select 2 players", 2, selected.size());
|
||||||
|
assertTrue("Should contain players from original list", players.containsAll(selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectRandomPlayers_returnsUniquePlayers() {
|
||||||
|
List<String> selected = gameEngine.selectRandomPlayers(players, 3);
|
||||||
|
|
||||||
|
// Vérifier l'unicité
|
||||||
|
assertEquals("Should have 3 unique players", 3, new ArrayList<>(selected).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectRandomPlayers_handlesCountLargerThanList() {
|
||||||
|
List<String> selected = gameEngine.selectRandomPlayers(players, 10);
|
||||||
|
|
||||||
|
assertEquals("Should select max available players", 4, selected.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_replacesJ1Placeholder() {
|
||||||
|
Question question = createQuestion("<J1> doit boire 2 gorgées");
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
assertFalse("Should not contain <J1> placeholder", text.contains("<J1>"));
|
||||||
|
assertTrue("Should contain a player name", containsAnyPlayer(text, players));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_replacesMultiplePlayerPlaceholders() {
|
||||||
|
Question question = createQuestion("<J1> et <J2> boivent ensemble");
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
assertFalse("Should not contain <J1>", text.contains("<J1>"));
|
||||||
|
assertFalse("Should not contain <J2>", text.contains("<J2>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_replacesVariante() {
|
||||||
|
Question question = createQuestion("C'est une question <variante> !");
|
||||||
|
question.setVariante(Arrays.asList("simple", "compliquée", "drôle"));
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertFalse("Should not contain <variante>", text.contains("<variante>"));
|
||||||
|
assertTrue("Should contain one of the variantes",
|
||||||
|
text.contains("simple") || text.contains("compliquée") || text.contains("drôle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_handlesDistribution() {
|
||||||
|
Question question = createQuestion("Question de test");
|
||||||
|
question.setDistribution(true);
|
||||||
|
question.setGorger(3);
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertTrue("Should contain 'distribue'", text.contains("distribue"));
|
||||||
|
assertTrue("Should contain gorgée count", text.contains("3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_handlesRecois() {
|
||||||
|
Question question = createQuestion("Question de test");
|
||||||
|
question.setRecois(true);
|
||||||
|
question.setGorger(2);
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertTrue("Should contain 'bois'", text.contains("bois"));
|
||||||
|
assertTrue("Should contain gorgée count", text.contains("2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_addsExtraGorgees() {
|
||||||
|
Question question = createQuestion("Question de test");
|
||||||
|
question.setDistribution(true);
|
||||||
|
question.setGorger(2);
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 3);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertTrue("Should contain total gorgée count (2+3=5)", text.contains("5"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_handlesManches() {
|
||||||
|
Question question = createQuestion("Défi à manches <manches> !");
|
||||||
|
question.setArret("Arrêtez maintenant !");
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
|
||||||
|
assertTrue("Should be marked as manche", processed.isManche);
|
||||||
|
assertTrue("Should have active manche", gameEngine.hasActiveManche());
|
||||||
|
assertNotNull("Manche should have end message", processed.question.getArretMessageManche());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateManches_decrementsMancheCount() {
|
||||||
|
// Créer une manche
|
||||||
|
Question question = createQuestion("Défi <manches>");
|
||||||
|
question.setArret("Fin !");
|
||||||
|
gameEngine.processQuestion(question, players, 0);
|
||||||
|
|
||||||
|
// Récupérer le nombre initial
|
||||||
|
GameEngine.MancheState initialState = gameEngine.updateManches();
|
||||||
|
int initialCount = initialState.activeManche.getManchesRestantes();
|
||||||
|
|
||||||
|
// Mettre à jour
|
||||||
|
GameEngine.MancheState updatedState = gameEngine.updateManches();
|
||||||
|
|
||||||
|
assertEquals("Manche count should decrease", initialCount - 1, updatedState.activeManche.getManchesRestantes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClearManches_removesAllManches() {
|
||||||
|
// Ajouter des manches
|
||||||
|
Question question = createQuestion("Défi <manches>");
|
||||||
|
question.setArret("Fin !");
|
||||||
|
gameEngine.processQuestion(question, players, 0);
|
||||||
|
|
||||||
|
gameEngine.clearManches();
|
||||||
|
|
||||||
|
assertFalse("Should have no active manches", gameEngine.hasActiveManche());
|
||||||
|
assertEquals("Should have 0 active manches", 0, gameEngine.getActiveManchesCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_handlesPluralGorgees() {
|
||||||
|
Question question = createQuestion("Test");
|
||||||
|
question.setDistribution(true);
|
||||||
|
question.setGorger(2);
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertTrue("Should use plural 'gorgées'", text.contains("gorgées"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessQuestion_handlesSingularGorgee() {
|
||||||
|
Question question = createQuestion("Test");
|
||||||
|
question.setDistribution(true);
|
||||||
|
question.setGorger(1);
|
||||||
|
|
||||||
|
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
|
||||||
|
String text = processed.question.getQuestion();
|
||||||
|
|
||||||
|
assertTrue("Should use singular 'gorgée'", text.contains("gorgée"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
|
||||||
|
private Question createQuestion(String text) {
|
||||||
|
Question q = new Question();
|
||||||
|
q.setId(1);
|
||||||
|
q.setQuestion(text);
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsAnyPlayer(String text, List<String> players) {
|
||||||
|
for (String player : players) {
|
||||||
|
if (text.contains(player)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user