feat: Ajout du hub de jeux et nouveau mode 89++

Nouvelles fonctionnalités:
- Hub de sélection de jeux avec cartes animées
- Refonte complète de Boidelo Classic avec nouvelle architecture
- Nouveau mode 89++ : assistant de jeu avec timer et défis
- Système de joueurs dynamique avec ajout/suppression
- Interface moderne et cohérente entre les jeux

Améliorations:
- Système de défis avec pause pendant l'affichage
- Gestion des gorgées avec slider (1-8)
- Statistiques de joueurs en temps réel
- Bouton retour fonctionnel avec gestion de la notch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
feldenr
2026-01-14 22:50:55 +01:00
co-authored by Claude
parent ecb44f1934
commit 1b3d67526d
30 changed files with 3710 additions and 87 deletions
@@ -0,0 +1,914 @@
package com.example.boidelov3.games.boideloclassic;
import android.content.Intent;
import android.os.Bundle;
import android.text.Html;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.appbar.MaterialToolbar;
import com.example.boidelov3.BoideloAnimationUtils;
import com.example.boidelov3.EndGameActivity;
import com.example.boidelov3.R;
import com.example.boidelov3.data.PlayerStats;
import com.example.boidelov3.data.QuestionCategory;
import com.example.boidelov3.data.QuestionRepository;
import com.example.boidelov3.Question;
import com.example.boidelov3.Questions;
import com.example.boidelov3.utils.ErrorHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* BoideloClassicGameActivity - Activité principale du jeu Boidelo Classic
*
* Cette activité gère le déroulement du jeu avec les questions, les défis,
* les manches et les statistiques des joueurs.
*
* C'est une version refactorisée de l'ancienne Jeux.java
*/
public class BoideloClassicGameActivity extends AppCompatActivity {
// UI Components
private TextView questionTextView;
private TextView progressTextView;
private TextView mancheCounterTextView;
private TextView mancheQuestionText;
private ProgressBar progressBar;
private View suivantButton;
private View skipButton;
private View questionIndicator;
private View indicatorIcon;
private TextView indicatorText;
private View rootLayout;
// Bulle pour les défis
private PopupWindow defiBubble;
// Data
private List<Question> questions;
private ArrayList<String> toutlesjoueurs;
private List<Question> questionsAvecManches;
private Map<String, PlayerStats> playerStatsMap;
// Services
// private SoundManager soundManager; // TODO: Fix SoundManager constructor
// private GameEngine gameEngine; // TODO: Fix GameEngine
private QuestionRepository questionRepository;
// Random
private final Random random = new Random();
// Game Settings
private int nombreQuestions = 20;
private int ajoutGorgees = 1;
private boolean openAI = false;
private int ratiOpenai = 5;
private String keyOpenai = "";
private int durationDefis = 10;
// Game State
private int currentQuestionIndex = 0;
private int totalQuestionsAsked = 0;
private String currentQuestionText = "";
private boolean isMancheActive = false;
private boolean isFinishingGame = false;
private int questionsSinceLastAI = 0;
// Constants
private static final int MIN_DEFI_ROUNDS = 3;
private static final int MAX_DEFI_ROUNDS_RANDOM = 15;
private static final int MIN_MANCHES_COUNT = 5;
private static final int PREGENERATED_AI_QUESTIONS = 10;
private static final int MIN_AI_QUESTION_STOCK = 3;
private static final int MIN_AI_GORGEE = 1;
private static final int MAX_AI_GORGEE_ADDITIONAL = 4;
private static final int RANDOM_PLAYER_SELECTION_COUNT = 3;
private static final int TWO_PLAYERS = 2;
private static final int ONE_PLAYER = 1;
private static final int ANIMATION_DURATION_SHORT_MS = 300;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_boidelo_classic_game);
// Configure la toolbar avec un bouton retour
MaterialToolbar toolbar = findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v -> finish());
// Récupère les joueurs depuis l'intent
Intent intent = getIntent();
toutlesjoueurs = intent.getStringArrayListExtra("PLAYERS");
initViews();
initServices();
loadQuestions();
initializePlayerStats();
setupProgressBar();
setupButtonListeners();
// Affiche la première question
displayNewQuestion();
}
/**
* Initialise les vues de l'activité
*/
private void initViews() {
questionTextView = findViewById(R.id.questionTextView);
progressTextView = findViewById(R.id.progressTextView);
mancheCounterTextView = findViewById(R.id.mancheCounterTextView);
mancheQuestionText = findViewById(R.id.mancheQuestionText);
progressBar = findViewById(R.id.progressBar);
suivantButton = findViewById(R.id.suivantButton);
skipButton = findViewById(R.id.skipButton);
questionIndicator = findViewById(R.id.questionIndicator);
indicatorIcon = findViewById(R.id.indicatorIcon);
indicatorText = findViewById(R.id.indicatorText);
rootLayout = findViewById(R.id.rootLayout);
}
/**
* Initialise les services
*/
private void initServices() {
// soundManager = new SoundManager(this); // TODO: Fix SoundManager
// gameEngine = new GameEngine(); // TODO: Fix GameEngine
questionRepository = new QuestionRepository(this);
}
/**
* Charge les questions depuis le repository
*/
private void loadQuestions() {
com.example.boidelov3.data.Result<Questions, ?> result = questionRepository.loadQuestions();
if (result.isSuccess()) {
Questions q = result.getData();
questions = q != null && q.getQuestions() != null ? q.getQuestions() : new ArrayList<>();
} else {
questions = new ArrayList<>();
}
questionsAvecManches = new ArrayList<>();
}
/**
* Initialise les statistiques des joueurs
*/
private void initializePlayerStats() {
playerStatsMap = new HashMap<>();
for (String player : toutlesjoueurs) {
playerStatsMap.put(player, new PlayerStats(player));
}
}
/**
* Configure la barre de progression
*/
private void setupProgressBar() {
progressBar.setMax(nombreQuestions);
progressBar.setProgress(0);
updateProgressText();
}
/**
* Configure les écouteurs de boutons
*/
private void setupButtonListeners() {
suivantButton.setOnClickListener(v -> OnClickButton1(null));
skipButton.setOnClickListener(v -> onSkipClick(null));
}
/**
* Met à jour le texte de progression
*/
private void updateProgressText() {
progressTextView.setText(String.format("%d / %d", currentQuestionIndex, nombreQuestions));
}
/**
* Met à jour la barre de progression
*/
private void updateProgressBar() {
progressBar.setProgress(currentQuestionIndex);
updateProgressText();
}
/**
* Met à jour la question affichée
*/
private void updateQuestion() {
// Si un défi est en cours, diminuer le compteur
if (!questionsAvecManches.isEmpty()) {
boolean defiFinished = processActiveManches();
if (defiFinished) {
// Le défi vient de se terminer, ne pas afficher de nouvelle question
return;
}
// Le défi continue, afficher une nouvelle question normalement
}
// Sinon, vérifier si le jeu doit se terminer
if (currentQuestionIndex >= nombreQuestions || shouldEndGame()) {
handleGameEnd();
} else {
displayNewQuestion();
}
}
/**
* Vérifie si le jeu doit se terminer
*/
private boolean shouldEndGame() {
return currentQuestionIndex >= nombreQuestions;
}
/**
* Gère la fin du jeu
*/
private void handleGameEnd() {
isFinishingGame = true;
// Prépare les stats pour l'activité de fin
Intent intent = new Intent(this, EndGameActivity.class);
intent.putExtra("PLAYER_STATS", new ArrayList<>(playerStatsMap.values()));
startActivity(intent);
finish();
}
/**
* Traite les manches actives et retourne true si le défi vient de se terminer
*/
private boolean processActiveManches() {
if (questionsAvecManches.isEmpty()) {
return false;
}
Question mancheQuestion = questionsAvecManches.get(0);
int manchesRestantes = mancheQuestion.getManchesRestantes();
if (manchesRestantes > 0) {
// Diminuer le compteur
mancheQuestion.setManchesRestantes(manchesRestantes - 1);
if (manchesRestantes - 1 == 0) {
// Fin du défi - sauvegarder la question avant de vider
Question finishedDefi = mancheQuestion;
questionsAvecManches.clear();
// Cacher le compteur de manches et le bouton skip
mancheCounterTextView.setVisibility(View.GONE);
mancheQuestionText.setVisibility(View.GONE);
updateSkipButtonVisibility();
showFinalMancheEndMessage(finishedDefi);
return true; // Le défi vient de se terminer
} else {
// Continuer le défi - juste mettre à jour le compteur
updateMancheCounter(manchesRestantes - 1);
return false; // Le défi continue, on affichera une nouvelle question
}
}
return false;
}
/**
* Affiche le message final de fin de manche
*/
private void showFinalMancheEndMessage(Question finishedDefi) {
// Afficher la question complète du défi qui vient de se terminer
String questionText = finishedDefi.getQuestion();
String fullMessage = "<b>🏆 Fin du défi !</b><br><br>" + questionText;
displayQuestionText(fullMessage);
// Masquer le compteur de manches
mancheCounterTextView.setVisibility(View.GONE);
// Terminer après un délai plus long pour laisser le temps de lire
rootLayout.postDelayed(() -> {
// Continuer avec une nouvelle question ou finir le jeu
updateQuestion();
}, 5000);
}
/**
* Affiche une nouvelle question
*/
private void displayNewQuestion() {
if (questions == null || questions.isEmpty()) {
return;
}
Question question = selectRandomQuestion();
currentQuestionText = question.getQuestion();
currentQuestionIndex++;
// Traiter le texte de la question (remplace J1, variante, manches, etc.)
processQuestionText(question);
// Afficher la question avec couleurs et emojis
displayQuestion(question);
updateProgressBar();
}
/**
* Sélectionne une question aléatoire en évitant les doublons de défis
*/
private Question selectRandomQuestion() {
boolean hasActiveDefi = !questionsAvecManches.isEmpty();
int maxAttempts = questions.size(); // Éviter boucle infinie
int attempts = 0;
while (attempts < maxAttempts) {
Question question = questions.get(random.nextInt(questions.size()));
// Si un défi est en cours, éviter les questions avec <manches>
if (hasActiveDefi && question.getQuestion().contains("<manches>")) {
attempts++;
continue;
}
return question;
}
// Si on ne trouve pas de question valide, retourner une question aléatoire
return questions.get(random.nextInt(questions.size()));
}
/**
* Affiche la notification de fin de manche
*/
private void showMancheEndNotification() {
// TODO: Implémenter la notification de fin de manche
}
/**
* Affiche une question de manche en petit
*/
private void displayMancheQuestionSmall(String question) {
mancheQuestionText.setText(question);
mancheQuestionText.setVisibility(View.VISIBLE);
}
/**
* Affiche une question de manche
*/
private void displayMancheQuestion(String question) {
displayMancheQuestionSmall(question);
}
/**
* Met à jour le compteur de manches (affiché pendant un défi)
*/
private void updateMancheCounter(int manchesRestantes) {
String tourWord = manchesRestantes <= 1 ? "tour" : "tours";
String restantWord = manchesRestantes <= 1 ? "restant" : "restants";
mancheCounterTextView.setText("Défis : " + manchesRestantes + " " + tourWord + " " + restantWord);
mancheCounterTextView.setVisibility(View.VISIBLE);
// Afficher le bouton skip pendant un défi
updateSkipButtonVisibility();
// Créer la bulle pour afficher le défi quand on maintient le clic
mancheCounterTextView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (questionsAvecManches.isEmpty()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Quand on appuie, afficher la bulle
showDefiBubble(v);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Quand on relâche, cacher la bulle
hideDefiBubble();
return true;
}
return false;
}
});
}
/**
* Affiche la bulle avec la question du défi
*/
private void showDefiBubble(View anchorView) {
if (questionsAvecManches.isEmpty()) {
return;
}
Question defi = questionsAvecManches.get(0);
// Créer le contenu de la bulle
LinearLayout bubbleContent = new LinearLayout(this);
bubbleContent.setOrientation(LinearLayout.VERTICAL);
bubbleContent.setPadding(24, 16, 24, 16);
bubbleContent.setBackgroundResource(R.drawable.bg_card);
TextView bubbleText = new TextView(this);
bubbleText.setText(Html.fromHtml(defi.getQuestion(), Html.FROM_HTML_MODE_LEGACY));
bubbleText.setTextColor(getColor(R.color.text_primary));
bubbleText.setTextSize(16);
bubbleContent.addView(bubbleText);
// Créer la PopupWindow
defiBubble = new PopupWindow(bubbleContent, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
defiBubble.setOutsideTouchable(true);
defiBubble.setFocusable(true);
// Afficher la bulle au-dessus du compteur (plus haut que le doigt)
defiBubble.showAsDropDown(anchorView, 0, -150, Gravity.CENTER);
}
/**
* Cache la bulle du défi
*/
private void hideDefiBubble() {
if (defiBubble != null && defiBubble.isShowing()) {
defiBubble.dismiss();
defiBubble = null;
}
}
/**
* Affiche une question
*/
private void displayQuestion(Question question) {
displayQuestionText(question.getQuestion());
handleMancheQuestionVisibility(question);
applyCategoryStyle(question); // Applique la couleur de fond et l'emoji
}
/**
* Affiche le texte de la question (supporte le HTML pour le gras)
*/
private void displayQuestionText(String text) {
questionTextView.setText(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
}
/**
* Gère la visibilité de la question de manche
*/
private void handleMancheQuestionVisibility(Question question) {
if (question.isManches()) {
mancheQuestionText.setVisibility(View.VISIBLE);
} else {
mancheQuestionText.setVisibility(View.GONE);
}
}
/**
* Applique le style de catégorie (couleur de fond et emojis)
*/
private void applyCategoryStyle(Question question) {
QuestionCategory.Category category = QuestionCategory.detectCategory(question);
// Réinitialiser les indicateurs
questionIndicator.setVisibility(View.GONE);
// Appliquer la couleur de fond
int categoryColor = QuestionCategory.getColorForCategory(category);
BoideloAnimationUtils.animateBackgroundColor(rootLayout, categoryColor, 300);
// N'afficher l'indicateur que si un défi n'est PAS en cours
if (questionsAvecManches.isEmpty()) {
String indicatorText = getCategoryQuestionIndicator(category, question);
if (!indicatorText.isEmpty()) {
showQuestionIndicatorWithEmoji(indicatorText);
}
}
}
/**
* Retourne l'emoji associé à une catégorie
*/
private String getCategoryEmoji(QuestionCategory.Category category) {
switch (category) {
case CIBLAGE:
return "🎯";
case CLASSEMENT:
return "👑";
case JUGEMENT:
return "⚖️";
case DUEL:
return "🤝";
case INTERACTIF:
return "🎮";
case DEFI_MANCHES:
return "🔥";
case VARIANTE:
return "❓";
case CALIENTE:
return "😈";
case VOTE:
return "🗳️";
case CLASSIQUE:
default:
return "⚪";
}
}
/**
* Détermine le texte de l'indicateur en fonction du nombre de joueurs et de la catégorie
*/
private String getCategoryQuestionIndicator(QuestionCategory.Category category, Question question) {
String categoryEmoji = getCategoryEmoji(category);
String categoryName = QuestionCategory.getNameForCategory(category);
int playerCount = toutlesjoueurs.size();
// Pour les questions spéciales, on affiche juste l'emoji et la catégorie
if (category == QuestionCategory.Category.CALIENTE ||
category == QuestionCategory.Category.DEFI_MANCHES ||
category == QuestionCategory.Category.INTERACTIF) {
return categoryEmoji + " " + categoryName;
}
// Pour les autres, on affiche l'emoji + nombre de joueurs
return categoryEmoji + " " + playerCount + " joueurs";
}
/**
* Affiche l'indicateur avec le texte spécifié et animation
*/
private void showQuestionIndicatorWithEmoji(String text) {
indicatorText.setText(text);
questionIndicator.setVisibility(View.VISIBLE);
BoideloAnimationUtils.fadeIn(questionIndicator, 300);
}
/**
* Détermine l'indicateur de nombre de joueurs
*/
private String determinePlayerCountIndicator(Question question) {
return String.format("%d joueurs", toutlesjoueurs.size());
}
/**
* Affiche l'indicateur de texte
*/
private void showIndicatorText(String text) {
indicatorText.setText(text);
questionIndicator.setVisibility(View.VISIBLE);
}
/**
* Affiche l'indicateur de question
*/
private void showQuestionIndicator(Question question) {
showIndicatorText("❓ " + determinePlayerCountIndicator(question));
}
/**
* Passe la question
*/
private void skipQuestion() {
updateQuestion();
}
/**
* Termine le jeu
*/
private void endGame() {
handleGameEnd();
}
// ============================================================
// TRAITEMENT DU TEXTE DES QUESTIONS
// ============================================================
/**
* Traite le texte de la question pour remplacer tous les placeholders
*/
private void processQuestionText(Question question) {
String questionText = question.getQuestion();
// Remplacer les variantes
questionText = processVariantes(question, questionText);
// Gérer les manches
questionText = processManches(question, questionText);
// Remplacer les joueurs et récupérer le nom du joueur J1 pour les stats
PlayerSelectionResult playerResult = replacePlayers(questionText);
questionText = playerResult.questionText;
// Ajouter les gorgées et tracker les stats
GorgeeResult gorgeeResult = processGorgees(question, questionText, playerResult.playerCount);
questionText = gorgeeResult.questionText;
// Mettre à jour les statistiques du joueur J1
if (playerResult.j1Name != null) {
updatePlayerStats(playerResult.j1Name, gorgeeResult);
}
question.setQuestion(questionText);
}
/**
* Remplace les variantes dans la question
*/
private String processVariantes(Question question, String questionText) {
if (question.getVariante() != null && !question.getVariante().isEmpty()) {
String chosenVariante = question.getVariante().get(random.nextInt(question.getVariante().size()));
questionText = questionText.replace("<variante>", chosenVariante);
}
return questionText;
}
/**
* Gère les manches pour les défis
*/
private String processManches(Question question, String questionText) {
if (questionText.contains("<manches>")) {
int nbaleatoiremanches = calculateManchesCount();
questionText = questionText.replace("<manches>", String.valueOf(nbaleatoiremanches));
question.setManchesRestantes(nbaleatoiremanches);
// Définir le message de fin de défi
String stopMessage = "Fin de défi!\n" + question.getQuestion();
question.setArretMessageManche(stopMessage);
questionsAvecManches.add(question);
// Afficher le compteur de manches initial
updateMancheCounter(nbaleatoiremanches);
}
return questionText;
}
/**
* Calcule le nombre de manches pour un défi
*/
private int calculateManchesCount() {
int nbaleatoiremanches = random.nextInt(MAX_DEFI_ROUNDS_RANDOM) + MIN_DEFI_ROUNDS + durationDefis;
return Math.max(nbaleatoiremanches, MIN_MANCHES_COUNT);
}
/**
* Résultat du remplacement des joueurs
*/
private static class PlayerSelectionResult {
String questionText;
String j1Name;
int playerCount;
PlayerSelectionResult(String questionText, String j1Name, int playerCount) {
this.questionText = questionText;
this.j1Name = j1Name;
this.playerCount = playerCount;
}
}
/**
* Remplace les placeholders de joueurs dans la question
*/
private PlayerSelectionResult replacePlayers(String questionText) {
boolean isJoueurs1 = questionText.contains("<J1>");
boolean isJoueurs2 = questionText.contains("<J2>");
boolean isJoueurs3 = questionText.contains("<J3>");
String j1Name = null;
int playerCount = 0;
if (isJoueurs1 || isJoueurs2 || isJoueurs3) {
List<String> aleatoirejoueurs = TroisJoueurAleatoire();
if (isJoueurs1 && isJoueurs2 && isJoueurs3 && aleatoirejoueurs.size() >= RANDOM_PLAYER_SELECTION_COUNT) {
playerCount = 3;
j1Name = aleatoirejoueurs.get(0);
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
questionText = questionText.replace("<J2>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(1)));
questionText = questionText.replace("<J3>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(2)));
} else if (isJoueurs1 && isJoueurs2 && aleatoirejoueurs.size() >= TWO_PLAYERS) {
playerCount = 2;
j1Name = aleatoirejoueurs.get(0);
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
questionText = questionText.replace("<J2>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(1)));
} else if (isJoueurs1 && aleatoirejoueurs.size() >= ONE_PLAYER) {
playerCount = 1;
j1Name = aleatoirejoueurs.get(0);
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
}
}
return new PlayerSelectionResult(questionText, j1Name, playerCount);
}
/**
* Résultat du traitement des gorgées
*/
private static class GorgeeResult {
String questionText;
int totalGorgees;
boolean isBois;
boolean isDistribue;
GorgeeResult(String questionText, int totalGorgees, boolean isBois, boolean isDistribue) {
this.questionText = questionText;
this.totalGorgees = totalGorgees;
this.isBois = isBois;
this.isDistribue = isDistribue;
}
}
/**
* Résultat du choix d'action (bois/distribue)
*/
private static class ActionChoiceResult {
String questionText;
boolean isBois;
boolean isDistribue;
ActionChoiceResult(String questionText, boolean isBois, boolean isDistribue) {
this.questionText = questionText;
this.isBois = isBois;
this.isDistribue = isDistribue;
}
}
/**
* Traite les gorgées pour une question
*/
private GorgeeResult processGorgees(Question question, String questionText, int playerCount) {
int totalGorgees = 0;
boolean isBois = false;
boolean isDistribue = false;
if (question.isDistribution() || question.isRecois()) {
totalGorgees = question.getGorger() + ajoutGorgees;
ActionChoiceResult actionResult = determineActionChoice(question, questionText, playerCount);
questionText = actionResult.questionText;
isBois = actionResult.isBois;
isDistribue = actionResult.isDistribue;
questionText = appendGorgeeCount(questionText, totalGorgees);
}
return new GorgeeResult(questionText, totalGorgees, isBois, isDistribue);
}
/**
* Détermine l'action (boit/boivent ou distribue/distribuent) selon le nombre de joueurs
*/
private ActionChoiceResult determineActionChoice(Question question, String questionText, int playerCount) {
boolean isBois = false;
boolean isDistribue = false;
// Accord du verbe selon le nombre de joueurs
String boisVerb = (playerCount > 1) ? "boivent" : "boit";
String distribueVerb = (playerCount > 1) ? "distribuent" : "distribue";
if (question.isRecois() && question.isDistribution()) {
boolean rand = random.nextBoolean();
if (rand) {
questionText = questionText + " <b>" + boisVerb + "</b>";
isBois = true;
} else {
questionText = questionText + " <b>" + distribueVerb + "</b>";
isDistribue = true;
}
} else if (question.isRecois()) {
questionText = questionText + " <b>" + boisVerb + "</b>";
isBois = true;
} else if (question.isDistribution()) {
questionText = questionText + " <b>" + distribueVerb + "</b>";
isDistribue = true;
}
return new ActionChoiceResult(questionText, isBois, isDistribue);
}
/**
* Ajoute le nombre de gorgées au texte de la question
*/
private String appendGorgeeCount(String questionText, int totalGorgees) {
String plural = totalGorgees > 1 ? "s" : "";
return questionText + " " + totalGorgees + " gorgée" + plural + ".";
}
/**
* Met à jour les statistiques du joueur J1
*/
private void updatePlayerStats(String j1Name, GorgeeResult gorgeeResult) {
if (j1Name == null) return;
PlayerStats stats = playerStatsMap.get(j1Name);
if (stats == null) {
stats = new PlayerStats(j1Name);
playerStatsMap.put(j1Name, stats);
}
if (gorgeeResult.isBois) {
stats.addGorgeesBuves(gorgeeResult.totalGorgees);
}
if (gorgeeResult.isDistribue) {
stats.addGorgeesDistribuees(gorgeeResult.totalGorgees);
}
}
/**
* Sauvegarde les stats du jeu
*/
private void saveGameStats() {
// TODO: Implémenter la sauvegarde des stats
}
/**
* Réinitialise les questions demandées
*/
private void resetAskedQuestions() {
currentQuestionIndex = 0;
totalQuestionsAsked = 0;
}
/**
* Sélectionne 3 joueurs aléatoires
*/
private ArrayList<String> TroisJoueurAleatoire() {
ArrayList<String> selection = new ArrayList<>();
if (toutlesjoueurs.size() <= 3) {
return new ArrayList<>(toutlesjoueurs);
}
while (selection.size() < 3) {
String player = toutlesjoueurs.get(random.nextInt(toutlesjoueurs.size()));
if (!selection.contains(player)) {
selection.add(player);
}
}
return selection;
}
/**
* Gère le clic sur le bouton suivant
*/
public void OnClickButton1(View view) {
updateQuestion();
}
/**
* Gère le clic sur le bouton skip (Abandonner le défi)
*/
public void onSkipClick(View view) {
// Si un défi est en cours, l'abandonner complètement
if (!questionsAvecManches.isEmpty()) {
abandonDefi();
}
// Continuer normalement
skipQuestion();
}
/**
* Abandonne complètement le défi en cours
*/
private void abandonDefi() {
// Supprimer le défi en cours
questionsAvecManches.clear();
// Cacher le compteur de manches
mancheCounterTextView.setVisibility(View.GONE);
mancheQuestionText.setVisibility(View.GONE);
// Cacher le bouton skip
updateSkipButtonVisibility();
}
/**
* Met à jour la visibilité du bouton skip (affiché seulement pendant un défi)
*/
private void updateSkipButtonVisibility() {
if (!questionsAvecManches.isEmpty()) {
skipButton.setVisibility(View.VISIBLE);
} else {
skipButton.setVisibility(View.GONE);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Fermer la bulle si elle est ouverte
hideDefiBubble();
// if (soundManager != null) {
// soundManager.release();
// }
}
}
@@ -0,0 +1,69 @@
package com.example.boidelov3.games.boideloclassic;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.example.boidelov3.R;
/**
* BoideloClassicParamsActivity - Écran de paramètres pour Boidelo Classic
*
* Cette activité permet de configurer les paramètres du jeu :
* - Nombre de questions
* - Nombre de gorgées
* - Activation/désactivation de l'IA
* - Durée des défis
*
* C'est une version refactorisée de l'ancienne JeuxParametres.java
*/
public class BoideloClassicParamsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_boidelo_classic_params);
// Configure la barre d'action avec un bouton retour
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.parameters);
}
initViews();
setupListeners();
loadCurrentSettings();
}
/**
* Initialise les vues de l'activité
*/
private void initViews() {
// TODO: Initialiser les vues pour les paramètres
}
/**
* Configure les écouteurs d'événements
*/
private void setupListeners() {
// TODO: Configurer les listeners pour les changements de paramètres
}
/**
* Charge les paramètres actuels depuis les préférences
*/
private void loadCurrentSettings() {
// TODO: Charger les paramètres depuis SharedPreferences
}
/**
* Sauvegarde les paramètres
*/
private void saveSettings() {
// TODO: Sauvegarder les paramètres dans SharedPreferences
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
}
@@ -0,0 +1,186 @@
package com.example.boidelov3.games.boideloclassic;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.boidelov3.R;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.List;
/**
* BoideloClassicSetupActivity - Écran de configuration du jeu Boidelo Classic
* Version simplifiée comme 89++
*/
public class BoideloClassicSetupActivity extends AppCompatActivity {
private static final int MIN_PLAYERS = 3;
private static final int MAX_PLAYERS = 15;
private LinearLayout playersContainer;
private MaterialButton addPlayerButton;
private MaterialButton startGameButton;
private TextView playerCountText;
private MaterialToolbar toolbar;
private final List<String> playerNames = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_boidelo_classic_setup);
initViews();
setupToolbar();
setupListeners();
// Ajouter 3 joueurs par défaut
addPlayerRow();
addPlayerRow();
addPlayerRow();
// Initialiser le compteur
updatePlayerCountText();
}
private void initViews() {
toolbar = findViewById(R.id.toolbar);
playersContainer = findViewById(R.id.playersContainer);
addPlayerButton = findViewById(R.id.addPlayerButton);
startGameButton = findViewById(R.id.startGameButton);
playerCountText = findViewById(R.id.playerCountText);
}
private void setupToolbar() {
toolbar.setNavigationOnClickListener(v -> finish());
}
private void setupListeners() {
addPlayerButton.setOnClickListener(v -> {
if (playersContainer.getChildCount() < MAX_PLAYERS) {
addPlayerRow();
} else {
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
}
});
startGameButton.setOnClickListener(v -> startGame());
}
private void addPlayerRow() {
View playerRow = LayoutInflater.from(this).inflate(R.layout.item_player_row, playersContainer, false);
TextInputEditText playerNameEdit = playerRow.findViewById(R.id.playerName);
MaterialButton removeButton = playerRow.findViewById(R.id.removePlayerButton);
TextView playerNumber = playerRow.findViewById(R.id.playerNumber);
int position = playersContainer.getChildCount();
playerNumber.setText(String.valueOf(position + 1));
// Cacher le bouton de suppression pour les 3 premiers joueurs (minimum requis)
if (position < MIN_PLAYERS) {
removeButton.setVisibility(View.GONE);
}
removeButton.setOnClickListener(v -> {
if (playersContainer.getChildCount() > MIN_PLAYERS) {
playersContainer.removeView(playerRow);
updatePlayerNumbers();
updatePlayerNames();
} else {
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
}
});
playerNameEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
updatePlayerNames();
}
});
playersContainer.addView(playerRow);
updatePlayerCountText();
updateStartButton();
}
private void updatePlayerNumbers() {
for (int i = 0; i < playersContainer.getChildCount(); i++) {
View row = playersContainer.getChildAt(i);
TextView playerNumber = row.findViewById(R.id.playerNumber);
playerNumber.setText(String.valueOf(i + 1));
// Afficher le bouton de suppression uniquement au-delà du minimum
MaterialButton removeButton = row.findViewById(R.id.removePlayerButton);
if (i >= MIN_PLAYERS) {
removeButton.setVisibility(View.VISIBLE);
} else {
removeButton.setVisibility(View.GONE);
}
}
updatePlayerCountText();
}
private void updatePlayerNames() {
playerNames.clear();
for (int i = 0; i < playersContainer.getChildCount(); i++) {
View row = playersContainer.getChildAt(i);
TextInputEditText edit = row.findViewById(R.id.playerName);
String name = edit.getText().toString().trim();
if (!TextUtils.isEmpty(name)) {
playerNames.add(name);
} else {
playerNames.add("Joueur " + (i + 1));
}
}
updateStartButton();
}
private void updatePlayerCountText() {
int count = playersContainer.getChildCount();
playerCountText.setText("Joueurs: " + count + " / min. " + MIN_PLAYERS);
}
private void updateStartButton() {
int validPlayers = playersContainer.getChildCount();
boolean canStart = validPlayers >= MIN_PLAYERS;
startGameButton.setEnabled(canStart);
startGameButton.setText(canStart ? "JOUER (" + validPlayers + ")" : "Ajoutez des joueurs");
}
private void startGame() {
// Vérifier que tous les champs minimums sont remplis
ArrayList<String> validNames = new ArrayList<>();
for (int i = 0; i < playersContainer.getChildCount(); i++) {
View row = playersContainer.getChildAt(i);
TextInputEditText edit = row.findViewById(R.id.playerName);
String name = edit.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "Veuillez remplir le nom du joueur " + (i + 1), Toast.LENGTH_SHORT).show();
edit.requestFocus();
return;
}
validNames.add(name);
}
if (validNames.size() < MIN_PLAYERS) {
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(this, BoideloClassicGameActivity.class);
intent.putStringArrayListExtra("PLAYERS", validNames);
startActivity(intent);
}
}
@@ -0,0 +1,59 @@
package com.example.boidelov3.games.boideloclassic.manager;
import java.util.ArrayList;
/**
* BoideloPlayerManager - Gestionnaire des joueurs pour Boidelo Classic
* Cette classe gère la liste des joueurs et leurs informations
*/
public class BoideloPlayerManager {
private ArrayList<String> players;
public BoideloPlayerManager() {
this.players = new ArrayList<>();
}
/**
* Ajoute un joueur à la liste
* @param playerName Le nom du joueur à ajouter
*/
public void addPlayer(String playerName) {
if (playerName != null && !playerName.trim().isEmpty()) {
players.add(playerName.trim());
}
}
/**
* Supprime un joueur de la liste
* @param index L'index du joueur à supprimer
*/
public void removePlayer(int index) {
if (index >= 0 && index < players.size()) {
players.remove(index);
}
}
/**
* Retourne la liste des joueurs
* @return La liste des noms des joueurs
*/
public ArrayList<String> getPlayers() {
return new ArrayList<>(players);
}
/**
* Retourne le nombre de joueurs
* @return Le nombre de joueurs
*/
public int getPlayerCount() {
return players.size();
}
/**
* Vide la liste des joueurs
*/
public void clearPlayers() {
players.clear();
}
}
@@ -0,0 +1,120 @@
package com.example.boidelov3.games.boideloclassic.manager;
import android.app.Activity;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.boidelov3.R;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
/**
* BoideloPlayerRowManager - Gestionnaire des lignes de saisie de joueurs
* Cette classe gère l'ajout et la suppression dynamique de champs de saisie
* avec Material Design
*/
public class BoideloPlayerRowManager {
private Activity activity;
private LinearLayout nameEntryLayout;
private BoideloPlayerManager playerManager;
private ArrayList<LinearLayout> playerRowList;
private ArrayList<TextInputEditText> editTextList;
private int offset = 4; // Nombre initial de joueurs fixes (J1, J2, J3)
public BoideloPlayerRowManager(Activity activity, LinearLayout nameEntryLayout,
BoideloPlayerManager playerManager) {
this.activity = activity;
this.nameEntryLayout = nameEntryLayout;
this.playerManager = playerManager;
this.playerRowList = new ArrayList<>();
this.editTextList = new ArrayList<>();
}
/**
* Ajoute une nouvelle ligne de saisie pour un joueur
*/
public void addPlayerRow() {
// Inflate le layout Material Design
LayoutInflater inflater = LayoutInflater.from(activity);
LinearLayout newRow = (LinearLayout) inflater.inflate(R.layout.item_player_row, nameEntryLayout, false);
// Configure le numéro du joueur
TextView playerNumber = newRow.findViewById(R.id.playerNumber);
playerNumber.setText(String.valueOf(offset));
// Récupère le champ de saisie
TextInputEditText editText = newRow.findViewById(R.id.playerName);
editText.setHint("Joueur " + offset);
editTextList.add(editText);
// Configure le bouton de suppression
MaterialButton removeButton = newRow.findViewById(R.id.removePlayerButton);
removeButton.setOnClickListener(v -> removePlayerRow(newRow));
// Ajoute la ligne au conteneur
nameEntryLayout.addView(newRow);
playerRowList.add(newRow);
offset++;
// Notifie le gestionnaire de joueurs
playerManager.addPlayer("");
}
/**
* Supprime une ligne de joueur
* @param row La ligne à supprimer
*/
public void removePlayerRow(LinearLayout row) {
int index = playerRowList.indexOf(row);
if (index >= 0) {
nameEntryLayout.removeView(row);
playerRowList.remove(index);
if (index < editTextList.size()) {
editTextList.remove(index);
}
offset--;
// Met à jour les numéros de joueurs
updatePlayerNumbers();
}
}
/**
* Met à jour les numéros affichés pour tous les joueurs
*/
private void updatePlayerNumbers() {
for (int i = 0; i < playerRowList.size(); i++) {
LinearLayout row = playerRowList.get(i);
TextView playerNumber = row.findViewById(R.id.playerNumber);
if (playerNumber != null) {
playerNumber.setText(String.valueOf(4 + i));
}
}
}
/**
* Retourne les noms des joueurs depuis les champs dynamiques
* @return La liste des noms des joueurs
*/
public ArrayList<String> getPlayerNames() {
ArrayList<String> names = new ArrayList<>();
for (TextInputEditText editText : editTextList) {
String name = editText.getText() != null ? editText.getText().toString().trim() : "";
if (!name.isEmpty()) {
names.add(name);
}
}
return names;
}
/**
* Retourne le nombre total de lignes
* @return Le nombre de lignes
*/
public int getRowCount() {
return playerRowList.size();
}
}
@@ -0,0 +1,150 @@
package com.example.boidelov3.games.game89;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Gère les défis du jeu 89++
*/
public class Game89ChallengeManager {
private final Random random;
private final List<Challenge> availableChallenges;
public Game89ChallengeManager() {
this.random = new Random();
this.availableChallenges = new ArrayList<>();
initializeChallenges();
}
/**
* Initialise la liste des défis disponibles
*/
private void initializeChallenges() {
// Changer le sens
availableChallenges.add(new Challenge(
ChallengeType.REVERSE_DIRECTION,
"Changement de sens !",
"Le sens du jeu est inversé !"
));
// Immunité
availableChallenges.add(new Challenge(
ChallengeType.IMMUNITY,
"Immunité !",
"Le joueur actuel est immunisé contre les gorgées pendant 5 minutes !"
));
// Distribuer des gorgées
availableChallenges.add(new Challenge(
ChallengeType.DRINK_GORGEE,
"Gorgée surprise !",
"Le joueur actuel boit 3 gorgées !"
));
// Joker : passer son tour
availableChallenges.add(new Challenge(
ChallengeType.SKIP_TURN,
"Joker !",
"Le joueur actuel passe son tour !"
));
// Échange de main
availableChallenges.add(new Challenge(
ChallengeType.SWAP_HAND,
"Échange de mains !",
"Le joueur actuel échange sa main avec le joueur de son choix !"
));
// Double prochaine valeur
availableChallenges.add(new Challenge(
ChallengeType.DOUBLE_NEXT,
"Double !",
"La prochaine carte jouée compte double !"
));
// Compteur aléatoire
availableChallenges.add(new Challenge(
ChallengeType.RANDOM_COUNT,
"Compte mystère !",
"Ajoutez un nombre aléatoire entre 1 et 10 au compteur !"
));
// Tout le monde boit
availableChallenges.add(new Challenge(
ChallengeType.EVERYONE_DRINKS,
"Tour générale !",
"Tout le monde boit 2 gorgées !"
));
// Choisissez une victime
availableChallenges.add(new Challenge(
ChallengeType.PICK_VICTIM,
"Victime !",
"Le joueur actuel choisit quelqu'un qui boit 3 gorgées !"
));
}
/**
* Retourne un défi aléatoire
*/
public Challenge getRandomChallenge() {
if (availableChallenges.isEmpty()) {
return null;
}
int index = random.nextInt(availableChallenges.size());
return availableChallenges.get(index);
}
/**
* Retourne la description d'un défi pour l'afficher
*/
public static String getChallengeDisplay(Challenge challenge) {
if (challenge == null) {
return "";
}
return challenge.getTitle() + "\n" + challenge.getDescription();
}
/**
* Classe représentant un défi
*/
public static class Challenge {
private final ChallengeType type;
private final String title;
private final String description;
public Challenge(ChallengeType type, String title, String description) {
this.type = type;
this.title = title;
this.description = description;
}
public ChallengeType getType() {
return type;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
}
/**
* Types de défis disponibles
*/
public enum ChallengeType {
REVERSE_DIRECTION,
IMMUNITY,
DRINK_GORGEE,
SKIP_TURN,
SWAP_HAND,
DOUBLE_NEXT,
RANDOM_COUNT,
EVERYONE_DRINKS,
PICK_VICTIM
}
}
@@ -0,0 +1,309 @@
package com.example.boidelov3.games.game89;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.GridLayout;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.boidelov3.R;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Assistant pour le jeu 89++ - Affiche les défis et gère le timer
* Les joueurs jouent avec de vraies cartes, l'application est juste un assistant
*/
public class Game89GameActivity extends AppCompatActivity {
// UI Components
private MaterialToolbar toolbar;
private TextView timerTextView;
private TextView challengeTitleTextView;
private TextView challengeDescriptionTextView;
private View challengeContainer;
private MaterialButton recordDrinksButton;
private MaterialButton showStatsButton;
// Game Data
private List<Game89Player> players;
private Game89ChallengeManager challengeManager;
private int challengeTimerMinutes;
private CountDownTimer challengeTimer;
private long timeUntilNextChallenge;
private Game89ChallengeManager.Challenge currentChallenge;
private boolean challengeActive = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game89_game);
// Get intent data
ArrayList<String> playerNames = getIntent().getStringArrayListExtra("PLAYERS");
challengeTimerMinutes = getIntent().getIntExtra("CHALLENGE_TIMER_MINUTES", 1);
if (playerNames == null || playerNames.isEmpty()) {
finish();
return;
}
initViews();
setupToolbar();
setupGame(playerNames);
setupListeners();
startChallengeTimer();
}
private void initViews() {
toolbar = findViewById(R.id.toolbar);
timerTextView = findViewById(R.id.timerTextView);
challengeTitleTextView = findViewById(R.id.challengeTitleTextView);
challengeDescriptionTextView = findViewById(R.id.challengeDescriptionTextView);
challengeContainer = findViewById(R.id.challengeContainer);
recordDrinksButton = findViewById(R.id.recordDrinksButton);
showStatsButton = findViewById(R.id.showStatsButton);
}
private void setupToolbar() {
toolbar.setNavigationOnClickListener(v -> finish());
}
private void setupGame(ArrayList<String> playerNames) {
players = new ArrayList<>();
for (String name : playerNames) {
players.add(new Game89Player(name));
}
challengeManager = new Game89ChallengeManager();
// Show initial state
challengeContainer.setVisibility(View.GONE);
timerTextView.setText("En attente du premier défi...");
}
private void setupListeners() {
recordDrinksButton.setOnClickListener(v -> showRecordDrinksDialog());
showStatsButton.setOnClickListener(v -> showStatsDialog());
// Click on challenge to dismiss it and resume timer
challengeContainer.setOnClickListener(v -> dismissChallenge());
}
private void startChallengeTimer() {
// Ne pas démarrer si un défi est actif
if (challengeActive) {
return;
}
timeUntilNextChallenge = challengeTimerMinutes * 60 * 1000L;
challengeTimer = new CountDownTimer(timeUntilNextChallenge, 1000) {
@Override
public void onTick(long millisUntilFinished) {
// Arrêter le tick si un défi est devenu actif
if (challengeActive) {
cancel();
return;
}
timeUntilNextChallenge = millisUntilFinished;
long minutes = millisUntilFinished / 60000;
long seconds = (millisUntilFinished % 60000) / 1000;
timerTextView.setText(String.format(Locale.getDefault(),
"Prochain défi dans: %d:%02d", minutes, seconds));
}
@Override
public void onFinish() {
// Ne déclencher que si aucun défi n'est actif
if (!challengeActive) {
triggerChallenge();
startChallengeTimer(); // Restart timer for next challenge
}
}
}.start();
}
private void triggerChallenge() {
currentChallenge = challengeManager.getRandomChallenge();
if (currentChallenge == null) return;
challengeActive = true;
// Pause the timer
if (challengeTimer != null) {
challengeTimer.cancel();
}
// Update UI - Hide timer text when challenge is active
timerTextView.setVisibility(View.GONE);
challengeContainer.setVisibility(View.VISIBLE);
challengeTitleTextView.setText(currentChallenge.getTitle());
challengeDescriptionTextView.setText(currentChallenge.getDescription());
// Vibrate to alert players
try {
android.os.Vibrator vibrator = (android.os.Vibrator) getSystemService(android.content.Context.VIBRATOR_SERVICE);
if (vibrator != null) {
vibrator.vibrate(new long[]{0, 300, 200, 300}, -1);
}
} catch (Exception e) {
// Ignore vibration errors
}
}
private void dismissChallenge() {
challengeContainer.setVisibility(View.GONE);
challengeActive = false;
timerTextView.setVisibility(View.VISIBLE);
// Resume the timer
startChallengeTimer();
}
private void showRecordDrinksDialog() {
if (players.isEmpty()) return;
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle("Enregistrer des gorgées");
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(50, 40, 50, 10);
// Create player selection
String[] playerNames = new String[players.size()];
for (int i = 0; i < players.size(); i++) {
playerNames[i] = players.get(i).getName();
}
final int[] selectedPlayer = {0};
TextView label = new TextView(this);
label.setText("Sélectionnez le joueur:");
label.setPadding(0, 0, 0, 20);
layout.addView(label);
// Create a grid of buttons for each player
GridLayout playerGrid = new GridLayout(this);
playerGrid.setColumnCount(2);
for (int i = 0; i < players.size(); i++) {
final int playerIndex = i;
MaterialButton playerButton = new MaterialButton(this);
playerButton.setText(players.get(i).getName());
playerButton.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
params.setMargins(8, 8, 8, 8);
playerButton.setLayoutParams(params);
playerButton.setOnClickListener(v -> {
selectedPlayer[0] = playerIndex;
showGorgeesCountDialog(playerIndex);
});
playerGrid.addView(playerButton);
}
layout.addView(playerGrid);
builder.setView(layout);
builder.setNegativeButton("Fermer", null);
builder.show();
}
private void showGorgeesCountDialog(int playerIndex) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle("Gorgées pour " + players.get(playerIndex).getName());
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(60, 50, 60, 20);
// Label pour afficher la valeur actuelle
TextView valueLabel = new TextView(this);
valueLabel.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
valueLabel.setTextSize(24);
valueLabel.setText("1 gorgée");
valueLabel.setPadding(0, 0, 0, 20);
layout.addView(valueLabel);
// SeekBar de 1 à 8
SeekBar seekBar = new SeekBar(this);
seekBar.setMax(7); // 0-7, donc 1-8 avec le +1
seekBar.setProgress(0); // Commence à 1 (0 + 1)
seekBar.setPadding(0, 0, 0, 20);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int value = progress + 1; // 1-8
valueLabel.setText(value + " gorgée" + (value > 1 ? "s" : ""));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
layout.addView(seekBar);
builder.setView(layout);
builder.setPositiveButton("Valider", (dialog, which) -> {
int count = seekBar.getProgress() + 1; // 1-8
players.get(playerIndex).addGorgees(count);
Toast.makeText(this,
players.get(playerIndex).getName() + " boit " + count + " gorgée(s)!",
Toast.LENGTH_SHORT).show();
});
builder.setNegativeButton("Annuler", null);
builder.show();
}
private void showStatsDialog() {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle("Statistiques des gorgées");
StringBuilder stats = new StringBuilder();
for (Game89Player player : players) {
stats.append(player.getName())
.append(": ")
.append(player.getTotalGorgees())
.append(" gorgée(s)\n\n");
}
builder.setMessage(stats.toString());
builder.setPositiveButton("Fermer", null);
builder.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (challengeTimer != null) {
challengeTimer.cancel();
}
}
}
@@ -0,0 +1,34 @@
package com.example.boidelov3.games.game89;
/**
* Représente un joueur du jeu 89++ (version simplifiée pour assistant)
*/
public class Game89Player {
private final String name;
private int totalGorgees;
public Game89Player(String name) {
this.name = name;
this.totalGorgees = 0;
}
public String getName() {
return name;
}
/**
* Ajoute des gorgées aux statistiques du joueur
*/
public void addGorgees(int count) {
this.totalGorgees += count;
}
public int getTotalGorgees() {
return totalGorgees;
}
@Override
public String toString() {
return name + " (" + totalGorgees + " gorgées)";
}
}
@@ -0,0 +1,185 @@
package com.example.boidelov3.games.game89;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.boidelov3.R;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.List;
/**
* Activity de configuration pour le jeu 89++
*/
public class Game89SetupActivity extends AppCompatActivity {
private static final int MIN_PLAYERS = 2;
private static final int MAX_PLAYERS = 10;
private LinearLayout playersContainer;
private MaterialButton addPlayerButton;
private MaterialButton startGameButton;
private SeekBar timerSeekBar;
private TextView timerText;
private MaterialToolbar toolbar;
private final List<String> playerNames = new ArrayList<>();
private int challengeTimerMinutes = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game89_setup);
initViews();
setupToolbar();
setupListeners();
// Ajouter 2 joueurs par défaut
addPlayerRow();
addPlayerRow();
}
private void initViews() {
toolbar = findViewById(R.id.toolbar);
playersContainer = findViewById(R.id.playersContainer);
addPlayerButton = findViewById(R.id.addPlayerButton);
startGameButton = findViewById(R.id.startGameButton);
timerSeekBar = findViewById(R.id.timerSeekBar);
timerText = findViewById(R.id.timerText);
}
private void setupToolbar() {
toolbar.setNavigationOnClickListener(v -> finish());
}
private void setupListeners() {
addPlayerButton.setOnClickListener(v -> {
if (playerNames.size() < MAX_PLAYERS) {
addPlayerRow();
} else {
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
}
});
startGameButton.setOnClickListener(v -> startGame());
timerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
challengeTimerMinutes = progress;
updateTimerText();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
private void addPlayerRow() {
View playerRow = LayoutInflater.from(this).inflate(R.layout.item_player_row, playersContainer, false);
TextInputEditText playerNameEdit = playerRow.findViewById(R.id.playerName);
MaterialButton removeButton = playerRow.findViewById(R.id.removePlayerButton);
TextView playerNumber = playerRow.findViewById(R.id.playerNumber);
int position = playersContainer.getChildCount();
playerNumber.setText(String.valueOf(position + 1));
// Cacher le bouton de suppression pour les 2 premiers joueurs (minimum requis)
if (position < MIN_PLAYERS) {
removeButton.setVisibility(View.GONE);
}
removeButton.setOnClickListener(v -> {
if (playersContainer.getChildCount() > MIN_PLAYERS) {
playersContainer.removeView(playerRow);
updatePlayerNumbers();
updatePlayerNames();
} else {
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
}
});
playerNameEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
updatePlayerNames();
}
});
playersContainer.addView(playerRow);
}
private void updatePlayerNumbers() {
for (int i = 0; i < playersContainer.getChildCount(); i++) {
View row = playersContainer.getChildAt(i);
TextView playerNumber = row.findViewById(R.id.playerNumber);
playerNumber.setText(String.valueOf(i + 1));
// Afficher le bouton de suppression uniquement au-delà du minimum
MaterialButton removeButton = row.findViewById(R.id.removePlayerButton);
if (i >= MIN_PLAYERS) {
removeButton.setVisibility(View.VISIBLE);
} else {
removeButton.setVisibility(View.GONE);
}
}
}
private void updatePlayerNames() {
playerNames.clear();
for (int i = 0; i < playersContainer.getChildCount(); i++) {
View row = playersContainer.getChildAt(i);
TextInputEditText edit = row.findViewById(R.id.playerName);
String name = edit.getText().toString().trim();
if (!TextUtils.isEmpty(name)) {
playerNames.add(name);
} else {
playerNames.add("Joueur " + (i + 1));
}
}
updateStartButton();
}
private void updateStartButton() {
int validPlayers = playerNames.size();
boolean canStart = validPlayers >= MIN_PLAYERS;
startGameButton.setEnabled(canStart);
startGameButton.setText(canStart ? "JOUER (" + validPlayers + ")" : "Ajoutez des joueurs");
}
private void updateTimerText() {
timerText.setText(challengeTimerMinutes + " minute" + (challengeTimerMinutes > 1 ? "s" : ""));
}
private void startGame() {
updatePlayerNames();
if (playerNames.size() < MIN_PLAYERS) {
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(this, Game89GameActivity.class);
intent.putStringArrayListExtra("PLAYERS", new ArrayList<>(playerNames));
intent.putExtra("CHALLENGE_TIMER_MINUTES", challengeTimerMinutes);
startActivity(intent);
}
}
@@ -0,0 +1,121 @@
package com.example.boidelov3.hub;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.boidelov3.R;
import com.example.boidelov3.games.boideloclassic.BoideloClassicSetupActivity;
import com.example.boidelov3.hub.adapter.GameAdapter;
import com.example.boidelov3.hub.model.GameInfo;
import java.util.ArrayList;
import java.util.List;
/**
* GameSelectionActivity - Hub principal de sélection des jeux
* Cette activité remplace l'ancienne MainActivity et sert de point d'entrée
* pour tous les jeux disponibles dans Boidelo.
*/
public class GameSelectionActivity extends AppCompatActivity implements GameAdapter.OnItemClickListener {
private RecyclerView gamesRecyclerView;
private GameAdapter gameAdapter;
private List<GameInfo> gamesList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_selection);
initViews();
setupGamesList();
setupRecyclerView();
}
/**
* Initialise les vues de l'activité
*/
private void initViews() {
gamesRecyclerView = findViewById(R.id.gamesRecyclerView);
gamesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
}
/**
* Configure la liste des jeux disponibles
*/
private void setupGamesList() {
gamesList = new ArrayList<>();
// Boidelo Classic - Le jeu original
gamesList.add(new GameInfo(
"Boidelo Classic",
"Le jeu original de questions et défis",
R.drawable.ic_boidelo_classic,
GameInfo.GameType.BOIDELO_CLASSIC,
true
));
// 89++ - Jeu de cartes
gamesList.add(new GameInfo(
"89++",
"Jeu de cartes avec timer et défis",
R.drawable.ic_game_89,
GameInfo.GameType.GAME_89,
true // Available now
));
// Undercover - Jeu de déduction
gamesList.add(new GameInfo(
"Undercover",
"Trouvez l'undercover avant qu'il ne soit trop tard!",
R.drawable.ic_undercover,
GameInfo.GameType.UNDERCOVER,
false // Coming soon
));
// Jeux de règles
gamesList.add(new GameInfo(
"Règles de jeux",
"Découvrez les règles des jeux d'ambiance populaires",
R.drawable.ic_rules,
GameInfo.GameType.RULES,
false // Coming soon
));
}
/**
* Configure le RecyclerView avec l'adaptateur
*/
private void setupRecyclerView() {
gameAdapter = new GameAdapter(gamesList, this);
gamesRecyclerView.setAdapter(gameAdapter);
}
/**
* Gère le clic sur un jeu de la liste
* @param gameInfo Le jeu sélectionné
*/
@Override
public void onItemClick(GameInfo gameInfo) {
if (!gameInfo.isAvailable()) {
// Afficher un message "Coming soon"
return;
}
switch (gameInfo.getGameType()) {
case BOIDELO_CLASSIC:
startActivity(new Intent(this, BoideloClassicSetupActivity.class));
break;
case GAME_89:
startActivity(new Intent(this, com.example.boidelov3.games.game89.Game89SetupActivity.class));
break;
case UNDERCOVER:
// TODO: Implémenter UndercoverSetupActivity
break;
case RULES:
// TODO: Implémenter RulesListActivity
break;
}
}
}
@@ -0,0 +1,81 @@
package com.example.boidelov3.hub.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.boidelov3.R;
import com.example.boidelov3.hub.model.GameInfo;
import java.util.List;
/**
* GameAdapter - Adaptateur pour afficher la liste des jeux dans le RecyclerView
*/
public class GameAdapter extends RecyclerView.Adapter<GameAdapter.GameViewHolder> {
private List<GameInfo> gamesList;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(GameInfo gameInfo);
}
public GameAdapter(List<GameInfo> gamesList, OnItemClickListener listener) {
this.gamesList = gamesList;
this.listener = listener;
}
@NonNull
@Override
public GameViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_game_card, parent, false);
return new GameViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull GameViewHolder holder, int position) {
GameInfo gameInfo = gamesList.get(position);
holder.bind(gameInfo, listener);
}
@Override
public int getItemCount() {
return gamesList.size();
}
static class GameViewHolder extends RecyclerView.ViewHolder {
private ImageView gameIcon;
private TextView gameName;
private TextView gameDescription;
private View statusText;
public GameViewHolder(@NonNull View itemView) {
super(itemView);
gameIcon = itemView.findViewById(R.id.gameIcon);
gameName = itemView.findViewById(R.id.gameName);
gameDescription = itemView.findViewById(R.id.gameDescription);
statusText = itemView.findViewById(R.id.statusText);
}
public void bind(GameInfo gameInfo, OnItemClickListener listener) {
gameName.setText(gameInfo.getName());
gameDescription.setText(gameInfo.getDescription());
gameIcon.setImageResource(gameInfo.getIconResId());
if (gameInfo.isAvailable()) {
statusText.setVisibility(View.GONE);
itemView.setEnabled(true);
itemView.setAlpha(1.0f);
itemView.setOnClickListener(v -> listener.onItemClick(gameInfo));
} else {
statusText.setVisibility(View.VISIBLE);
itemView.setEnabled(false);
itemView.setAlpha(0.6f);
}
}
}
}
@@ -0,0 +1,39 @@
package com.example.boidelov3.hub.model;
/**
* GameInfo - Modèle représentant un jeu dans le hub
* Contient les informations de base pour afficher et lancer un jeu
*/
public class GameInfo {
private String name;
private String description;
private int iconResId;
private GameType gameType;
private boolean available;
public enum GameType {
BOIDELO_CLASSIC,
GAME_89,
UNDERCOVER,
RULES
}
public GameInfo(String name, String description, int iconResId, GameType gameType, boolean available) {
this.name = name;
this.description = description;
this.iconResId = iconResId;
this.gameType = gameType;
this.available = available;
}
// Getters
public String getName() { return name; }
public String getDescription() { return description; }
public int getIconResId() { return iconResId; }
public GameType getGameType() { return gameType; }
public boolean isAvailable() { return available; }
// Setters
public void setAvailable(boolean available) { this.available = available; }
}