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
parent ecb44f1934
commit 1b3d67526d
30 changed files with 3710 additions and 87 deletions
+36 -3
View File
@@ -15,15 +15,48 @@
android:theme="@style/Theme.BoideloV3"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<activity
android:name=".hub.GameSelectionActivity"
android:exported="true"
android:theme="@style/Theme.BoideloV3">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Boidelo Classic Activities -->
<activity
android:name=".games.boideloclassic.BoideloClassicSetupActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".games.boideloclassic.BoideloClassicGameActivity"
android:configChanges="orientation|screenSize"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".games.boideloclassic.BoideloClassicParamsActivity"
android:exported="false"
android:screenOrientation="portrait" />
<!-- Game 89++ Activities -->
<activity
android:name=".games.game89.Game89SetupActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".games.game89.Game89GameActivity"
android:configChanges="orientation|screenSize"
android:exported="false"
android:screenOrientation="portrait" />
<!-- Legacy Activities (to be removed) -->
$1
<activity
android:name="com.example.boidelov3.Jeux"
android:configChanges="orientation|screenSize"
@@ -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; }
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<solid android:color="@color/card_background"/>
<corners android:radius="16dp"/>
<stroke
android:width="1dp"
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/primary"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/accent"
android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM7,7h2v2H7V7zM11,7h2v2h-2V7zM15,7h2v2h-2V7zM7,11h2v2H7v-2zM11,11h2v2h-2v-2zM15,11h2v2h-2v-2zM7,15h10v2H7v-2z" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/accent"
android:pathData="M18,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z" />
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/primary"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" />
</vector>
@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".games.boideloclassic.BoideloClassicGameActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="Boidelo Classic"
app:titleTextColor="@color/text_on_primary" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Progress Bar -->
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="8dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:max="100"
android:progress="0"
android:progressTint="@color/accent"
android:progressBackgroundTint="@color/surface_variant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Progress Text -->
<TextView
android:id="@+id/progressTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Question 1 / 50"
android:textColor="@color/text_secondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progressBar" />
<!-- Question Type Indicator -->
<LinearLayout
android:id="@+id/questionIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progressTextView">
<ImageView
android:id="@+id/indicatorIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_player_one"
app:tint="@color/text_secondary" />
<TextView
android:id="@+id/indicatorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="1 joueur"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Main Question Container -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/questionCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:layout_constraintBottom_toTopOf="@id/buttonContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/questionIndicator">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp">
<!-- Manche Counter -->
<TextView
android:id="@+id/mancheCounterTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_card"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="Manches: 5"
android:textColor="@color/primary"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
tools:visibility="visible" />
<!-- Manche Question Text (petit) -->
<TextView
android:id="@+id/mancheQuestionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:gravity="center"
android:text="Défi en cours"
android:textColor="@color/text_secondary"
android:textSize="14sp"
android:textStyle="italic"
android:visibility="gone" />
<!-- Question Text -->
<TextView
android:id="@+id/questionTextView"
style="@style/BoideloQuestionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Question"
android:textSize="26sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Button Container -->
<LinearLayout
android:id="@+id/buttonContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="32dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<!-- Skip Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/skipButton"
style="@style/BoideloButton"
android:layout_width="229dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:onClick="onSkipClick"
android:text="Abandonner le défi"
android:textSize="12sp"
android:textColor="@color/error"
android:visibility="gone"
app:backgroundTint="@color/surface_variant"
app:cornerRadius="28dp" />
<!-- Next Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/suivantButton"
style="@style/BoideloButton.Primary"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_weight="2"
android:onClick="OnClickButton1"
android:text="Suivant"
android:textSize="18sp"
app:cornerRadius="28dp"
app:icon="@android:drawable/ic_media_play"
app:iconGravity="textEnd" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/surface"
tools:context=".games.boideloclassic.BoideloClassicParamsActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="@string/parameters"
app:titleTextColor="@color/text_on_primary" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Title -->
<TextView
style="@style/BoideloTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:text="@string/game_settings" />
<!-- Game Settings Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Number of Questions -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/number_of_questions"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<SeekBar
android:id="@+id/questionsSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="50"
android:min="10"
android:progress="20" />
<TextView
android:id="@+id/questionsCountText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:textColor="@color/accent"
android:textSize="14sp"
android:textStyle="bold"
tools:text="20 questions" />
<!-- Number of Gorgées -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/number_of_gorgees"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<SeekBar
android:id="@+id/gorgeesSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="5"
android:min="1"
android:progress="1" />
<TextView
android:id="@+id/gorgeesCountText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:textColor="@color/accent"
android:textSize="14sp"
android:textStyle="bold"
tools:text="+1 gorgée" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- AI Settings Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/artificial_intelligence"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/aiSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/enable_ai_questions"
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Save Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/saveButton"
style="@style/BoideloButton.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:fitsSystemWindows="true"
tools:context=".games.boideloclassic.BoideloClassicSetupActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="Boidelo Classic"
app:titleTextColor="@android:color/white" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Title -->
<TextView
style="@style/BoideloTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:text="@string/boidelo_classic_setup_title"
android:textColor="@color/primary" />
<!-- Game Description Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Le jeu original de questions et défis"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:gravity="center" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Players Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/players"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/playerCountText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/min_players_required"
android:textColor="@color/accent"
android:textSize="14sp"
android:textStyle="bold"
android:gravity="center" />
<LinearLayout
android:id="@+id/playersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical" />
<com.google.android.material.button.MaterialButton
android:id="@+id/addPlayerButton"
style="@style/BoideloButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/add_player"
app:icon="@android:drawable/ic_input_add" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Start Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/startGameButton"
style="@style/BoideloButton.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/start_game"
android:textSize="18sp"
app:icon="@android:drawable/ic_media_play" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
tools:context=".games.game89.Game89GameActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="89++ - Assistant"
app:titleTextColor="@android:color/white" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="24dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Timer Card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/timerCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/primary"
app:cardCornerRadius="24dp"
app:cardElevation="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Assistant 89++"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/timerTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Prochain défi dans: 2:00"
android:textColor="@android:color/white"
android:textSize="32sp"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Jouez avec vos cartes physiques !"
android:textColor="@android:color/white"
android:textSize="14sp"
android:alpha="0.9" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Challenge Display -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/challengeContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="@color/accent"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/timerCard"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚠️ DÉFI !"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/challengeTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Changement de sens !"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/challengeDescriptionTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Le sens du jeu est inversé !"
android:textColor="@android:color/white"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="👆 Tapez pour continuer"
android:textColor="@android:color/white"
android:textSize="12sp"
android:alpha="0.7"
android:layout_gravity="center" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Action Buttons -->
<LinearLayout
android:id="@+id/actionButtons"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/challengeContainer">
<com.google.android.material.button.MaterialButton
android:id="@+id/recordDrinksButton"
style="@style/BoideloButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="🍺 Enregistrer\ngorgées"
android:textColor="@color/text_primary"
android:textSize="14sp"
app:backgroundTint="@android:color/white"
app:cornerRadius="20dp"
app:strokeColor="@color/primary"
app:strokeWidth="2dp"
android:elevation="2dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showStatsButton"
style="@style/BoideloButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="📊 Voir\nstatistiques"
android:textColor="@color/text_primary"
android:textSize="14sp"
app:backgroundTint="@android:color/white"
app:cornerRadius="20dp"
app:strokeColor="@color/primary"
app:strokeWidth="2dp"
android:elevation="2dp" />
</LinearLayout>
<!-- Info Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardBackgroundColor="@android:color/white"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionButtons">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💡 Rappel des règles"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="• Valet = -10\n• Dame = Change sens\n• Roi = Choisissez la valeur\n• Dizaine (10, 20...) = distribuez des gorgées\n• 70 = DOUBLE (14 gorgées!)"
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/surface"
tools:context=".games.game89.Game89SetupActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="89++ - Configuration"
app:titleTextColor="@color/text_on_primary" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Title -->
<TextView
style="@style/BoideloTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:text="@string/game_89_title"
android:textColor="@color/primary" />
<!-- Game Rules Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/game_rules"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/game_89_rules_summary"
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Players Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/players"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/playersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical" />
<com.google.android.material.button.MaterialButton
android:id="@+id/addPlayerButton"
style="@style/BoideloButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/add_player"
app:icon="@android:drawable/ic_input_add" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Challenge Timer Card -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/challenge_timer"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<SeekBar
android:id="@+id/timerSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="2"
android:min="1"
android:progress="1" />
<TextView
android:id="@+id/timerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="1 minute"
android:textColor="@color/accent"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Start Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/startGameButton"
style="@style/BoideloButton.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/start_game"
android:textSize="18sp"
app:icon="@android:drawable/ic_media_play" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:fitsSystemWindows="true"
tools:context=".hub.GameSelectionActivity">
<!-- App Bar -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:title="@string/app_name"
app:titleTextColor="@color/text_on_primary"
android:paddingStart="8dp"
android:paddingEnd="8dp" />
</com.google.android.material.appbar.AppBarLayout>
<!-- Main Content -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Welcome Title -->
<TextView
style="@style/BoideloTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"
android:gravity="center"
android:text="@string/welcome_to_hub" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:gravity="center"
android:text="@string/select_game_subtitle"
android:textColor="@color/text_hint"
android:textSize="14sp" />
<!-- Games RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gamesRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_game_card" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:strokeColor="@color/card_stroke"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp">
<!-- Game Icon Container -->
<com.google.android.material.card.MaterialCardView
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@color/accent"
app:cardCornerRadius="8dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/gameIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:contentDescription="@string/game_icon"
android:src="@drawable/ic_player_three"
app:tint="@color/text_primary" />
</com.google.android.material.card.MaterialCardView>
<!-- Game Info -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/gameName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
tools:text="Boidelo Classic" />
<TextView
android:id="@+id/gameDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/text_hint"
android:textSize="14sp"
android:maxLines="2"
android:ellipsize="end"
tools:text="Le jeu original de questions et défis" />
<!-- Coming Soon Badge -->
<com.google.android.material.chip.Chip
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/coming_soon"
android:textColor="@color/text_hint"
android:textSize="11sp"
android:visibility="gone"
app:chipBackgroundColor="@color/surface_variant"
app:chipCornerRadius="4dp"
app:chipMinHeight="24dp"
style="@style/Widget.Material3.Chip.Assist.Elevated" />
</LinearLayout>
<!-- Play Indicator -->
<com.google.android.material.card.MaterialCardView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
app:cardBackgroundColor="@color/primary"
app:cardCornerRadius="20dp"
app:cardElevation="2dp">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:contentDescription="@string/play_game"
android:src="@android:drawable/ic_media_play"
app:tint="@color/text_on_primary" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="8dp"
app:cardElevation="2dp"
app:strokeColor="@color/card_stroke"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<!-- Player Number Indicator -->
<TextView
android:id="@+id/playerNumber"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/bg_button_secondary"
android:gravity="center"
android:textColor="@color/text_primary"
android:textSize="12sp"
android:textStyle="bold"
android:text="4" />
<!-- Player Name Input -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="@string/player_name_hint"
app:boxBackgroundColor="@color/white"
app:boxStrokeColor="@color/primary"
app:boxStrokeWidth="1dp"
app:endIconMode="clear_text"
app:hintTextColor="@color/text_hint"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/playerName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:maxLines="1"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Remove Button with Material Design -->
<com.google.android.material.button.MaterialButton
android:id="@+id/removePlayerButton"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/remove_player"
app:icon="@android:drawable/ic_delete"
app:iconTint="@color/error"
app:backgroundTint="@color/surface"
app:iconSize="20dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
+37 -35
View File
@@ -1,50 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Mode SOMBRE - Amélioré pour lisibili-->
<!-- Mode SOMBRE - Nouvelle palette adape -->
<!-- Fond - gris moyen pour meilleur contraste -->
<color name="background">#1E1E1E</color>
<color name="surface">#2D2D2D</color>
<color name="surface_variant">#3A3A3A</color>
<color name="card_background">#2D2D2D</color>
<!-- Arrière-plans - fond sombre avec la nouvelle palette -->
<color name="background">#000807</color>
<color name="surface">#0D0D1A</color>
<color name="surface_variant">#1A1A2E</color>
<color name="card_background">#141428</color>
<!-- Texte - blanc pour contraste maximal -->
<color name="text_primary">#FFFFFF</color>
<color name="text_secondary">#E0E0E0</color>
<color name="text_hint">#9E9E9E</color>
<!-- Texte - blanc et variantes de la nouvelle palette -->
<color name="text_primary">#FBF9FF</color>
<color name="text_secondary">#B3B7EE</color>
<color name="text_hint">#9395D3</color>
<color name="text_on_primary">#000807</color>
<!-- Couleur principale - violet clair sur fond sombre -->
<color name="primary">#B388FF</color>
<color name="primary_dark">#7C4DFF</color>
<color name="primary_light">#D1C4E9</color>
<!-- Couleur principale - adaptée du mode clair -->
<color name="primary">#B3B7EE</color>
<color name="primary_dark">#9395D3</color>
<color name="accent">#B3B7EE</color>
<!-- Couleurs de jeu - plus claires pour être visibles -->
<color name="game_normal">#2D2D2D</color>
<color name="game_background">#1E1E1E</color>
<color name="game_question_standard">#2D2D2D</color>
<color name="game_question_1player">#372E45</color>
<color name="game_question_2players">#413E5A</color>
<color name="game_question_3players">#4B4E6F</color>
<color name="game_question_manche">#2E3E5A</color>
<color name="game_manche_end">#5A4E2E</color>
<color name="game_finished">#5A3E2E</color>
<!-- Couleurs de jeu - adaptées pour mode sombre -->
<color name="game_normal">#0D0D1A</color>
<color name="game_background">#000807</color>
<color name="game_question_standard">#141428</color>
<color name="game_question_1player">#1E1E35</color>
<color name="game_question_2players">#2A2A45</color>
<color name="game_question_3players">#373755</color>
<color name="game_question_manche">#2A2A45</color>
<color name="game_manche_end">#3A3520</color>
<color name="game_finished">#3A2520</color>
<!-- États -->
<color name="success">#81C784</color>
<color name="error">#EF5350</color>
<color name="warning">#FFB74D</color>
<!-- Pour compatibilité -->
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="accent">#B388FF</color>
<color name="accent_dark">#E0E0E0</color>
<color name="primary_container">#4A3A69</color>
<color name="on_primary_container">#EDE7F6</color>
<color name="card_stroke">#404040</color>
<color name="divider">#404040</color>
<color name="text_on_game_primary">#FFFFFF</color>
<color name="text_on_colored_dark">#FFFFFF</color>
<!-- Conteneurs -->
<color name="primary_container">#2A2A45</color>
<color name="on_primary_container">#FBF9FF</color>
<color name="card_stroke">#2A2A45</color>
<color name="divider">#2A2A45</color>
<!-- Overlays -->
<color name="overlay_dark">#80000000</color>
<color name="overlay_light">#80FFFFFF</color>
<!-- Pour compatibilité -->
<color name="white">#FBF9FF</color>
<color name="black">#000807</color>
</resources>
+37 -36
View File
@@ -1,50 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- REFACTORING COMPLET - Palette Simple et Contrastée -->
<!-- Mode CLAIR -->
<color name="background">#FFFFFF</color>
<color name="surface">#FFFFFF</color>
<color name="surface_variant">#F0F0F0</color>
<!-- NOUVELLE PALETTE - Design cohérent et accessible -->
<!-- Couleurs principales -->
<color name="primary">#9395D3</color>
<color name="primary_dark">#000807</color>
<color name="accent">#B3B7EE</color>
<!-- Arrière-plans -->
<color name="background">#FBF9FF</color>
<color name="surface">#FBF9FF</color>
<color name="surface_variant">#FBF9FF</color>
<color name="card_background">#FFFFFF</color>
<!-- Texte - contraste maximal -->
<color name="text_primary">#000000</color>
<color name="text_secondary">#333333</color>
<color name="text_hint">#999999</color>
<!-- Couleur principale - violet visible -->
<color name="primary">#7C4DFF</color>
<color name="primary_dark">#6200EA</color>
<color name="primary_light">#B388FF</color>
<!-- Couleurs de jeu - très subtiles, lisibles -->
<color name="game_normal">#FFFFFF</color>
<color name="game_background">#FFFFFF</color>
<!-- Texte - contraste optimal -->
<color name="text_primary">#000807</color>
<color name="text_secondary">#A2A3BB</color>
<color name="text_hint">#9395D3</color>
<color name="text_on_primary">#FBF9FF</color>
<!-- Couleurs de jeu -->
<color name="game_normal">#FBF9FF</color>
<color name="game_background">#FBF9FF</color>
<color name="game_question_standard">#FFFFFF</color>
<color name="game_question_1player">#F3E5F5</color>
<color name="game_question_2players">#E1BEE7</color>
<color name="game_question_3players">#CE93D8</color>
<color name="game_question_manche">#E8EAF6</color>
<color name="game_question_1player">#E8EAF6</color>
<color name="game_question_2players">#D1C4E9</color>
<color name="game_question_3players">#B3B7EE</color>
<color name="game_question_manche">#C5CAE9</color>
<color name="game_manche_end">#FFF59D</color>
<color name="game_finished">#FFCCBC</color>
<!-- États -->
<color name="success">#4CAF50</color>
<color name="error">#F44336</color>
<color name="warning">#FF9800</color>
<!-- Pour compatibilité -->
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="accent">#7C4DFF</color>
<color name="accent_dark">#333333</color>
<color name="primary_container">#EDE7F6</color>
<color name="on_primary_container">#1A0033</color>
<!-- Conteneurs -->
<color name="primary_container">#E8EAF6</color>
<color name="on_primary_container">#000807</color>
<color name="card_stroke">#E0E0E0</color>
<color name="divider">#E0E0E0</color>
<color name="text_on_game_primary">#000000</color>
<color name="text_on_colored_dark">#FFFFFF</color>
<color name="divider">#B3B7EE</color>
<!-- Overlays -->
<color name="overlay_dark">#80000000</color>
<color name="overlay_light">#80FFFFFF</color>
<!-- Pour compatibilité -->
<color name="white">#FFFFFF</color>
<color name="black">#000807</color>
</resources>
+56
View File
@@ -20,5 +20,61 @@
<string name="activer_les_questions_par_chatgpt">Activer les questions par IA</string>
<string name="cl_api_openai">Clé API</string>
<string name="openai">Intelligence Artificielle</string>
<!-- Hub Strings -->
<string name="welcome_to_hub">Bienvenue sur Boidelo Hub</string>
<string name="select_game_subtitle">Choisissez un jeu pour commencer</string>
<string name="game_icon">Icône du jeu</string>
<string name="play_game">Jouer</string>
<string name="coming_soon">Bientôt disponible</string>
<string name="boidelo_classic_description">Le jeu original de questions et défis</string>
<string name="game_89_description">Jeu de cartes avec timer et défis</string>
<string name="undercover_description">Trouvez l\'undercover avant qu\'il ne soit trop tard!</string>
<string name="rules_description">Découvrez les règles des jeux d\'ambiance populaires</string>
<!-- Boidelo Classic Strings -->
<string name="boidelo_classic_title">Boidelo Classic</string>
<string name="boidelo_classic_setup_title">Configuration de la partie</string>
<string name="player_name_hint">Nom du joueur</string>
<string name="min_players_required">Joueurs: 0 / min. 3</string>
<string name="player_setup_instructions">Pour commencer, entrez les noms des joueurs (minimum 3)</string>
<string name="add_player">Ajouter un joueur</string>
<string name="start_game">JOUER</string>
<string name="settings">Paramètres</string>
<string name="parameters">Paramètres</string>
<string name="game_settings">Paramètres du jeu</string>
<string name="number_of_questions">Nombre de questions</string>
<string name="number_of_gorgees">Nombre de gorgées</string>
<string name="artificial_intelligence">Intelligence Artificielle</string>
<string name="enable_ai_questions">Activer les questions par IA</string>
<string name="save">Enregistrer</string>
<string name="skip">Passer</string>
<string name="next">Suivant</string>
<string name="remove_player">Supprimer ce joueur</string>
<!-- Game 89++ Strings -->
<string name="game_89_title">89++</string>
<string name="game_89_rules_summary">Le but est d\'arriver à un compte de 89 ou de le dépasser. Le joueur qui arrive/dépasse 89 perd!\n\nValet = -10 | Dame = Change sens | Roi = Choisissez la valeur\n\nÀ chaque dizaine (10, 20, 30…), distribuez des gorgées égales à la dizaine (sauf 70 = double)!</string>
<string name="game_rules">Règles du jeu</string>
<string name="players">Joueurs</string>
<string name="challenge_timer">Timer des défis</string>
<string name="game_89_play_card">Jouer une carte</string>
<string name="game_89_mistake">Erreur de calcul</string>
<string name="game_89_counter">Compteur</string>
<string name="game_89_clockwise">↻ Sens horaire</string>
<string name="game_89_counterclockwise">↺ Sens anti-horaire</string>
<string name="game_89_current_player">Joueur actuel</string>
<string name="game_89_your_cards">Vos cartes</string>
<string name="game_89_next_challenge">Prochain défi dans:</string>
<string name="game_89_challenge_title">DÉFI!</string>
<string name="game_89_select_value">Sélectionnez la valeur (1-10):</string>
<string name="game_89_announce_count">Annoncez le compte:</string>
<string name="game_89_actual_count">Compte actuel:</string>
<string name="game_89_ten_multiplier">DIZAINE! %d gorgées à distribuer!</string>
<string name="game_89_seventy_bonus">70 = DOUBLE! 14 gorgées!</string>
<string name="game_89_game_over">Le jeu est terminé!</string>
<string name="game_89_winner">%s a gagné!</string>
<string name="game_89_loser">%s a dépassé 89 et perd!</string>
<string name="game_89_final_stats">Statistiques finales:</string>
<!-- Legacy strings (to be removed after refactor) -->
<string name="test_de_connectivit_openai">Tester la connexion</string>
</resources>
+12 -12
View File
@@ -19,27 +19,27 @@
<!-- Bouton primaire -->
<style name="BoideloButton.Primary">
<item name="backgroundTint">@color/primary</item>
<item name="android:textColor">@color/white</item>
<item name="rippleColor">@color/primary_light</item>
<item name="android:textColor">@color/text_on_primary</item>
<item name="rippleColor">@color/primary_dark</item>
</style>
<!-- Bouton secondaire -->
<style name="BoideloButton.Secondary">
<item name="backgroundTint">@color/accent</item>
<item name="android:textColor">@color/text_primary</item>
<item name="rippleColor">@color/accent_dark</item>
<item name="rippleColor">@color/primary</item>
</style>
<!-- Bouton danger -->
<style name="BoideloButton.Danger">
<item name="backgroundTint">@color/error</item>
<item name="android:textColor">@color/white</item>
<item name="android:textColor">@color/text_on_primary</item>
</style>
<!-- Bouton succès -->
<style name="BoideloButton.Success">
<item name="backgroundTint">@color/success</item>
<item name="android:textColor">@color/white</item>
<item name="android:textColor">@color/text_on_primary</item>
</style>
<!-- ============================================================ -->
@@ -98,17 +98,17 @@
<!-- ============================================================ -->
<style name="BoideloCard" parent="Widget.Material3.CardView.Elevated">
<item name="cardBackgroundColor">@color/white</item>
<item name="cardBackgroundColor">@color/card_background</item>
<item name="cardCornerRadius">16dp</item>
<item name="cardElevation">4dp</item>
<item name="contentPadding">16dp</item>
</style>
<style name="BoideloCard.Outlined">
<item name="cardBackgroundColor">@color/white</item>
<item name="cardBackgroundColor">@color/card_background</item>
<item name="cardCornerRadius">16dp</item>
<item name="cardElevation">0dp</item>
<item name="strokeColor">@color/surface_variant</item>
<item name="strokeColor">@color/card_stroke</item>
<item name="strokeWidth">1dp</item>
</style>
@@ -119,7 +119,7 @@
<style name="BoideloSeekBar" parent="Widget.AppCompat.SeekBar">
<item name="colorAccent">@color/accent</item>
<item name="android:progressTint">@color/accent</item>
<item name="android:progressBackgroundTint">@color/surface_variant</item>
<item name="android:progressBackgroundTint">@color/divider</item>
</style>
<!-- ============================================================ -->
@@ -128,7 +128,7 @@
<style name="BoideloProgressBar" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressTint">@color/accent</item>
<item name="android:progressBackgroundTint">@color/surface_variant</item>
<item name="android:progressBackgroundTint">@color/divider</item>
</style>
<!-- ============================================================ -->
@@ -137,7 +137,7 @@
<style name="BoideloToolbar" parent="Widget.Material3.Toolbar">
<item name="android:background">@color/primary</item>
<item name="titleTextColor">@color/white</item>
<item name="titleTextColor">@color/text_on_primary</item>
<item name="android:elevation">4dp</item>
</style>
@@ -147,7 +147,7 @@
<style name="BoideloChip" parent="Widget.Material3.Chip.Suggestion">
<item name="chipBackgroundColor">@color/accent</item>
<item name="chipStrokeColor">@color/accent_dark</item>
<item name="chipStrokeColor">@color/primary</item>
<item name="android:textColor">@color/text_primary</item>
</style>