feat: Ajout du jeu Papelito, améliorations UX et corrections de bugs
Nouveau jeu: - Ajout du jeu Papelito (Undercover) avec flow complet - Configuration des joueurs, temps de discussion, votes - Système d'élimination et gestion des égalités - Interface Material Design avec cartes et dialogues Corrections de bugs critiques: - Fix crash Papelito au lancement (MaterialSwitch vs Switch) - Fix crash lors des votes nuls (égalité entre joueurs) - Fix crash fin de partie lors du retour (navigation vers hub) - Fix visibilité texte questions (couleur dynamique) - Fix compteur tours défis invisible (blanc sur blanc) - Fix icone question manquante pendant défis Améliorations UX Boidelo Classic: - Harmonisation des couleurs dynamiques (toolbar, bouton) - Bouton de réglages maintenant visible (MaterialButton) - Conteneur IA se rétracte quand désactivé - Meilleure gestion des couleurs selon catégorie - Fix délai entre manches pour affichage message fin Améliorations techniques: - Mise à jour CLAUDE.md avec architecture Papelito - Amélioration tests unitaires (GameEngine, PlayerStats, QuestionCategory) - Standardisation des clés Intent entre activités - Nettoyage code mort (méthodes non utilisées) Tests: - 302 tests unitaires passants - Couverture GameEngine, PlayerStats, QuestionCategory - Tests Papelito (game logic, player management) - Tests Game89 (challenges, players) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -82,7 +82,12 @@ public class EndGameActivity extends AppCompatActivity {
|
||||
questionsPlayed = getIntent().getIntExtra("EXTRA_QUESTIONS_PLAYED", 0);
|
||||
playersCount = getIntent().getIntExtra("EXTRA_PLAYERS_COUNT", 0);
|
||||
players = getIntent().getStringArrayListExtra("EXTRA_PLAYERS");
|
||||
playerStatsList = getIntent().getParcelableArrayListExtra("EXTRA_PLAYER_STATS");
|
||||
|
||||
// Essayer avec les deux clés possibles (PLAYER_STATS ou EXTRA_PLAYER_STATS)
|
||||
playerStatsList = getIntent().getParcelableArrayListExtra("PLAYER_STATS");
|
||||
if (playerStatsList == null) {
|
||||
playerStatsList = getIntent().getParcelableArrayListExtra("EXTRA_PLAYER_STATS");
|
||||
}
|
||||
|
||||
// Si pas de données, utiliser les SharedPreferences
|
||||
if (questionsPlayed == 0) {
|
||||
@@ -177,20 +182,20 @@ public class EndGameActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne à l'écran d'accueil
|
||||
* Retourne à l'écran d'accueil (hub de jeux)
|
||||
*/
|
||||
private void goToHome() {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
Intent intent = new Intent(this, com.example.boidelov3.hub.GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relance une nouvelle partie
|
||||
* Relance une nouvelle partie (retourne au hub)
|
||||
*/
|
||||
private void replay() {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
Intent intent = new Intent(this, com.example.boidelov3.hub.GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
@@ -87,9 +87,9 @@ public class Jeux extends AppCompatActivity {
|
||||
private int questionsSinceLastAI = 0; // Compteur pour le ratio IA
|
||||
|
||||
// Constantes pour les nombres magiques
|
||||
private static final int MIN_DEFI_ROUNDS = 3; // Minimum 3 manches pour les défis
|
||||
private static final int MAX_DEFI_ROUNDS_RANDOM = 5; // Max 5 tours aléatoires en plus (3-8 tours au total)
|
||||
private static final int MIN_MANCHES_COUNT = 1;
|
||||
private static final int MIN_DEFI_ROUNDS = 4; // Minimum 4 manches pour les défis
|
||||
private static final int MAX_DEFI_ROUNDS_RANDOM = 6; // Max 6 tours aléatoires (4-10 tours au total)
|
||||
private static final int MIN_MANCHES_COUNT = 4;
|
||||
private static final int PREGENERATED_AI_QUESTIONS = 2;
|
||||
private static final int MIN_AI_QUESTION_STOCK = 2;
|
||||
private static final int MIN_AI_GORGEE = 1; // Minimum 1 gorgée
|
||||
|
||||
@@ -118,11 +118,11 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
int initialQuestions = 50;
|
||||
int initialGorgees = 0;
|
||||
int initialRatio = 8;
|
||||
int initialDuration = 0; // 0 pour avoir 3-8 tours par défaut (MIN_DEFI_ROUNDS=3)
|
||||
int initialDuration = 0; // 0 pour avoir 4-10 tours par défaut (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
questionCountValue.setText(String.valueOf(initialQuestions));
|
||||
gorgeesValue.setText(String.valueOf(initialGorgees));
|
||||
durationValue.setText("0"); // Afficher 0 par défaut pour avoir 3-8 tours
|
||||
durationValue.setText("0"); // Afficher 0 par défaut pour avoir 4-10 tours
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + initialRatio);
|
||||
|
||||
@@ -150,7 +150,7 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
seekBarDuration.setMin(-5); // Permet un offset négatif jusqu'à -5
|
||||
}
|
||||
seekBarDuration.setMax(15);
|
||||
seekBarDuration.setProgress(0); // Valeur par défaut à 0 pour avoir 3-8 tours (MIN_DEFI_ROUNDS=3)
|
||||
seekBarDuration.setProgress(0); // Valeur par défaut à 0 pour avoir 4-10 tours (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
// Configuration des listeners pour les seekBars
|
||||
seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@@ -11,5 +11,15 @@ public class Questions {
|
||||
return questions;
|
||||
}
|
||||
|
||||
// autres getters et setters...
|
||||
public void setQuestions(List<Question> questions) {
|
||||
this.questions = questions;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class GameEngine {
|
||||
|
||||
// Gérer les manches
|
||||
if (questionText.contains("<manches>")) {
|
||||
int manchesCount = random.nextInt(10) + 5;
|
||||
int manchesCount = random.nextInt(7) + 4; // 4-10 manches
|
||||
questionText = questionText.replace("<manches>", String.valueOf(manchesCount));
|
||||
|
||||
// Créer une copie de la question pour la manche active
|
||||
|
||||
+147
-16
@@ -1,6 +1,7 @@
|
||||
package com.example.boidelov3.games.boideloclassic;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.view.Gravity;
|
||||
@@ -12,6 +13,8 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.example.boidelov3.BoideloAnimationUtils;
|
||||
import com.example.boidelov3.EndGameActivity;
|
||||
@@ -39,12 +42,13 @@ import java.util.Random;
|
||||
public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
|
||||
// UI Components
|
||||
private MaterialToolbar toolbar;
|
||||
private TextView questionTextView;
|
||||
private TextView progressTextView;
|
||||
private TextView mancheCounterTextView;
|
||||
private TextView mancheQuestionText;
|
||||
private ProgressBar progressBar;
|
||||
private View suivantButton;
|
||||
private com.google.android.material.button.MaterialButton suivantButton;
|
||||
private View skipButton;
|
||||
private View questionIndicator;
|
||||
private View indicatorIcon;
|
||||
@@ -85,9 +89,9 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
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 MIN_DEFI_ROUNDS = 4; // Minimum 4 manches
|
||||
private static final int MAX_DEFI_ROUNDS_RANDOM = 6; // Max 6 tours aléatoires (4-10 tours au total)
|
||||
private static final int MIN_MANCHES_COUNT = 4; // Minimum 4 manches
|
||||
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;
|
||||
@@ -102,21 +106,37 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
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
|
||||
// Récupère les joueurs et les paramètres depuis l'intent
|
||||
Intent intent = getIntent();
|
||||
toutlesjoueurs = intent.getStringArrayListExtra("PLAYERS");
|
||||
|
||||
// Récupérer les paramètres de jeu
|
||||
if (intent.hasExtra("EXTRA_NOMBRE_QUESTIONS")) {
|
||||
nombreQuestions = intent.getIntExtra("EXTRA_NOMBRE_QUESTIONS", 20);
|
||||
}
|
||||
if (intent.hasExtra("EXTRA_AJOUT_GORGEE")) {
|
||||
ajoutGorgees = intent.getIntExtra("EXTRA_AJOUT_GORGEE", 1);
|
||||
}
|
||||
if (intent.hasExtra("EXTRA_OPENAI")) {
|
||||
openAI = intent.getBooleanExtra("EXTRA_OPENAI", false);
|
||||
}
|
||||
if (intent.hasExtra("EXTRA_RATIO_OPENAI")) {
|
||||
ratiOpenai = intent.getIntExtra("EXTRA_RATIO_OPENAI", 5);
|
||||
}
|
||||
if (intent.hasExtra("EXTRA_KEY_OPENAI")) {
|
||||
keyOpenai = intent.getStringExtra("EXTRA_KEY_OPENAI");
|
||||
}
|
||||
if (intent.hasExtra("EXTRA_DURATION_DEFIS")) {
|
||||
durationDefis = intent.getIntExtra("EXTRA_DURATION_DEFIS", 0);
|
||||
}
|
||||
|
||||
initViews();
|
||||
initServices();
|
||||
loadQuestions();
|
||||
initializePlayerStats();
|
||||
setupProgressBar();
|
||||
setupButtonListeners();
|
||||
|
||||
|
||||
// Affiche la première question
|
||||
displayNewQuestion();
|
||||
}
|
||||
@@ -125,6 +145,9 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
* Initialise les vues de l'activité
|
||||
*/
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
questionTextView = findViewById(R.id.questionTextView);
|
||||
progressTextView = findViewById(R.id.progressTextView);
|
||||
mancheCounterTextView = findViewById(R.id.mancheCounterTextView);
|
||||
@@ -485,15 +508,123 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
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);
|
||||
}
|
||||
// Harmoniser les couleurs de la toolbar et du bouton
|
||||
harmonizeUiColors(categoryColor);
|
||||
|
||||
// Afficher l'indicateur pour toutes les questions (y compris pendant les défis)
|
||||
String indicatorText = getCategoryQuestionIndicator(category, question);
|
||||
if (!indicatorText.isEmpty()) {
|
||||
showQuestionIndicatorWithEmoji(indicatorText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Harmonise les couleurs de la toolbar et du bouton avec la couleur de fond
|
||||
*/
|
||||
private void harmonizeUiColors(int baseColor) {
|
||||
// Créer une teinte plus foncée pour la toolbar (25% plus foncée pour plus de contraste)
|
||||
int toolbarColor = darkenColor(baseColor, 0.25f);
|
||||
|
||||
// Créer une teinte plus foncée pour le bouton (15% plus foncée pour être plus visible)
|
||||
int buttonColor = darkenColor(baseColor, 0.15f);
|
||||
|
||||
// Créer une teinte très foncée pour la progressBar et texte (30% plus foncée)
|
||||
int accentColor = darkenColor(baseColor, 0.3f);
|
||||
|
||||
// Déterminer la couleur du texte selon la luminosité du fond
|
||||
int textColor = isColorDark(baseColor) ? Color.WHITE : Color.BLACK;
|
||||
int lighterTextColor = isColorDark(baseColor) ?
|
||||
lightenColor(Color.WHITE, 0.3f) : // Gris clair pour fond sombre
|
||||
darkenColor(Color.BLACK, 0.3f); // Gris foncé pour fond clair
|
||||
|
||||
// Appliquer la couleur à la toolbar
|
||||
toolbar.setBackgroundColor(toolbarColor);
|
||||
|
||||
// Appliquer la couleur au bouton suivant avec plus de contraste
|
||||
if (suivantButton != null) {
|
||||
suivantButton.setBackgroundTintList(
|
||||
ContextCompat.getColorStateList(this, android.R.color.transparent)
|
||||
);
|
||||
suivantButton.setBackgroundColor(buttonColor);
|
||||
// Texte toujours blanc pour le bouton pour plus de lisibilité
|
||||
suivantButton.setTextColor(Color.WHITE);
|
||||
}
|
||||
|
||||
// Appliquer la couleur à la progressBar
|
||||
if (progressBar != null) {
|
||||
progressBar.setProgressTintList(
|
||||
android.content.res.ColorStateList.valueOf(accentColor)
|
||||
);
|
||||
}
|
||||
|
||||
// Appliquer la couleur aux textes
|
||||
if (progressTextView != null) {
|
||||
progressTextView.setTextColor(lighterTextColor);
|
||||
}
|
||||
|
||||
// Pour le compteur de manches, utiliser une couleur foncée car il est dans une carte (bg_card)
|
||||
if (mancheCounterTextView != null) {
|
||||
// Toujours utiliser du gris foncé pour le compteur (il est sur fond de carte)
|
||||
mancheCounterTextView.setTextColor(Color.parseColor("#424242"));
|
||||
}
|
||||
|
||||
if (mancheQuestionText != null) {
|
||||
mancheQuestionText.setTextColor(textColor);
|
||||
}
|
||||
|
||||
// Adapter aussi la couleur de l'indicateur (icône et texte)
|
||||
if (indicatorText != null) {
|
||||
indicatorText.setTextColor(textColor);
|
||||
}
|
||||
|
||||
if (indicatorIcon != null && indicatorIcon instanceof android.widget.ImageView) {
|
||||
android.widget.ImageView imageView = (android.widget.ImageView) indicatorIcon;
|
||||
imageView.setColorFilter(textColor, android.graphics.PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
// NE PAS changer la couleur du texte de la question - garder la couleur par défaut
|
||||
}
|
||||
|
||||
/**
|
||||
* Assombrit une couleur d'un certain pourcentage
|
||||
*/
|
||||
private int darkenColor(int color, float percent) {
|
||||
int alpha = Color.alpha(color);
|
||||
int red = Color.red(color);
|
||||
int green = Color.green(color);
|
||||
int blue = Color.blue(color);
|
||||
|
||||
red = Math.max((int) (red * (1 - percent)), 0);
|
||||
green = Math.max((int) (green * (1 - percent)), 0);
|
||||
blue = Math.max((int) (blue * (1 - percent)), 0);
|
||||
|
||||
return Color.argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Éclaircit une couleur d'un certain pourcentage
|
||||
*/
|
||||
private int lightenColor(int color, float percent) {
|
||||
int alpha = Color.alpha(color);
|
||||
int red = Color.red(color);
|
||||
int green = Color.green(color);
|
||||
int blue = Color.blue(color);
|
||||
|
||||
red = Math.min((int) (red + (255 - red) * percent), 255);
|
||||
green = Math.min((int) (green + (255 - green) * percent), 255);
|
||||
blue = Math.min((int) (blue + (255 - blue) * percent), 255);
|
||||
|
||||
return Color.argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine si une couleur est foncée (pour choisir la couleur du texte)
|
||||
*/
|
||||
private boolean isColorDark(int color) {
|
||||
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
|
||||
return darkness >= 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'emoji associé à une catégorie
|
||||
*/
|
||||
|
||||
+466
-34
@@ -1,24 +1,62 @@
|
||||
package com.example.boidelov3.games.boideloclassic;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import android.widget.EditText;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.OpenAIService;
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.utils.ErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private SeekBar seekBar1, seekBar2, seekBar3, seekBarDuration;
|
||||
private TextView textView1, textView2, textView5, textViewRatioGen, questionCountValue, gorgeesValue, durationValue;
|
||||
private SwitchMaterial checkBoxGPT;
|
||||
private Button buttonTestApi;
|
||||
private EditText editTextKeyGPT;
|
||||
private AutoCompleteTextView autoCompleteProvider;
|
||||
private com.google.android.material.card.MaterialCardView openaiCard;
|
||||
private LinearLayout openaiContentLayout;
|
||||
private String keyGPT;
|
||||
private OpenAIService.AIProvider selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
private int nbQuestions;
|
||||
|
||||
private ArrayList<String> toutlesjoueurs;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_boidelo_classic_params);
|
||||
|
||||
@@ -28,37 +66,431 @@ public class BoideloClassicParamsActivity extends AppCompatActivity {
|
||||
getSupportActionBar().setTitle(R.string.parameters);
|
||||
}
|
||||
|
||||
initViews();
|
||||
setupListeners();
|
||||
loadCurrentSettings();
|
||||
// Initialisation des vues
|
||||
seekBar1 = findViewById(R.id.seekBar1);
|
||||
seekBar2 = findViewById(R.id.seekBar2);
|
||||
seekBar3 = findViewById(R.id.seekBar3);
|
||||
seekBarDuration = findViewById(R.id.seekBarDuration);
|
||||
textView1 = findViewById(R.id.textView1);
|
||||
textView2 = findViewById(R.id.textView2);
|
||||
textView5 = findViewById(R.id.textView5);
|
||||
editTextKeyGPT = findViewById(R.id.editTextGPT);
|
||||
autoCompleteProvider = findViewById(R.id.autoCompleteProvider);
|
||||
buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
textViewRatioGen = findViewById(R.id.textViewRatioGen);
|
||||
questionCountValue = findViewById(R.id.questionCountValue);
|
||||
gorgeesValue = findViewById(R.id.gorgeesValue);
|
||||
durationValue = findViewById(R.id.durationValue);
|
||||
openaiCard = findViewById(R.id.openaiCard);
|
||||
|
||||
// Récupérer le LinearLayout qui contient tous les éléments de la carte IA
|
||||
openaiContentLayout = findViewById(R.id.openaiCardContent);
|
||||
|
||||
// Configuration du dropdown pour le provider IA
|
||||
String[] providers = new String[]{
|
||||
OpenAIService.AIProvider.OPENAI.getDisplayName(),
|
||||
OpenAIService.AIProvider.OPENROUTER.getDisplayName(),
|
||||
OpenAIService.AIProvider.ZAI.getDisplayName()
|
||||
};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, providers);
|
||||
autoCompleteProvider.setAdapter(adapter);
|
||||
|
||||
// Charger le provider sauvegardé
|
||||
SharedPreferences providerPrefs = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
String savedProvider = providerPrefs.getString("aiProvider", OpenAIService.AIProvider.OPENAI.getDisplayName());
|
||||
autoCompleteProvider.setText(savedProvider, false);
|
||||
|
||||
// Définir le provider sélectionné
|
||||
if (savedProvider.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (savedProvider.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
|
||||
// Listener pour le changement de provider
|
||||
autoCompleteProvider.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String selected = (String) parent.getItemAtPosition(position);
|
||||
SharedPreferences.Editor editor = providerPrefs.edit();
|
||||
editor.putString("aiProvider", selected);
|
||||
editor.apply();
|
||||
|
||||
if (selected.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (selected.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialiser les TextView avec les valeurs par défaut
|
||||
int initialQuestions = 50;
|
||||
int initialGorgees = 0;
|
||||
int initialRatio = 8;
|
||||
int initialDuration = 0; // 0 pour avoir 4-10 tours par défaut (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
questionCountValue.setText(String.valueOf(initialQuestions));
|
||||
gorgeesValue.setText(String.valueOf(initialGorgees));
|
||||
durationValue.setText("0"); // Afficher 0 par défaut pour avoir 4-10 tours
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + initialRatio);
|
||||
|
||||
// Configuration de la seekBar1
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBar1.setMin(20);
|
||||
}
|
||||
seekBar1.setMax(150);
|
||||
seekBar1.setProgress(50);
|
||||
|
||||
// Configuration de la seekBar2
|
||||
seekBar2.setMax(20);
|
||||
seekBar2.setProgress(0);
|
||||
|
||||
// Configuration de la seekBar3
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBar2.setMin(0);
|
||||
seekBar3.setMin(1);
|
||||
}
|
||||
seekBar3.setMax(15);
|
||||
seekBar3.setProgress(8);
|
||||
|
||||
// Configuration de la seekBarDuration (permet valeurs négatives pour offset)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBarDuration.setMin(-5); // Permet un offset négatif jusqu'à -5
|
||||
}
|
||||
seekBarDuration.setMax(15);
|
||||
seekBarDuration.setProgress(0); // Valeur par défaut à 0 pour avoir 4-10 tours (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
// Configuration des listeners pour les seekBars
|
||||
seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Ajustement de la valeur au multiple de 10 le plus proche
|
||||
int adjustedProgress = Math.round(progress / 10) * 10;
|
||||
seekBar.setProgress(adjustedProgress);
|
||||
questionCountValue.setText(String.valueOf(adjustedProgress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Mise à jour du gorgeesValue en fonction de la valeur de la seekBar2
|
||||
gorgeesValue.setText(String.valueOf(progress));
|
||||
// Mise à jour du textView5 en fonction de la valeur de la seekBar2
|
||||
switch (progress) {
|
||||
case 0:
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
break;
|
||||
case 2:
|
||||
textView5.setText("Palier : Petite merde");
|
||||
break;
|
||||
case 4:
|
||||
textView5.setText("Palier : Petit joueur");
|
||||
break;
|
||||
case 6:
|
||||
textView5.setText("Palier : Un p'tit verre ?!");
|
||||
break;
|
||||
case 8:
|
||||
textView5.setText("Palier : ça commence à aller");
|
||||
break;
|
||||
case 10:
|
||||
textView5.setText("Palier : Alcoolique");
|
||||
break;
|
||||
case 12:
|
||||
textView5.setText("Palier : COMA ETHYLIX");
|
||||
break;
|
||||
case 13:
|
||||
textView5.setText("Palier : APÉROOOOO !!");
|
||||
break;
|
||||
case 14:
|
||||
textView5.setText("Palier : LA J'SUIS BIENG");
|
||||
break;
|
||||
case 15:
|
||||
textView5.setText("Palier : J'VOIS PLUS RIENG");
|
||||
break;
|
||||
case 17:
|
||||
textView5.setText("Palier : J'AI PLUS DE VERRES");
|
||||
break;
|
||||
case 18 :
|
||||
textView5.setText("Palier : Soirée Murge");
|
||||
break;
|
||||
case 19:
|
||||
textView5.setText("Palier : Soirée Pétée");
|
||||
break;
|
||||
case 20:
|
||||
textView5.setText("Palier : L'ENDER DRAGON");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration du checkBox // Q : IL sert à quoi ?
|
||||
// R : Il sert à activer/désactiver les vues en dessous
|
||||
|
||||
buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
|
||||
checkBoxGPT = findViewById(R.id.checkBoxGPT);
|
||||
checkBoxGPT.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
updateOpenAICardState(isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialiser l'état de la carte IA selon l'état initial du checkBox
|
||||
updateOpenAICardState(checkBoxGPT.isChecked());
|
||||
|
||||
// Configuration de la seekBar3
|
||||
seekBar3.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar3, int progress, boolean fromUser) {
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar3) {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration de la seekBarDuration
|
||||
seekBarDuration.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Afficher avec signe +/- pour bien voir l'offset, mais sans signe pour 0
|
||||
String displayValue;
|
||||
if (progress > 0) {
|
||||
displayValue = "+" + progress;
|
||||
} else {
|
||||
displayValue = String.valueOf(progress); // Affiche "0" ou "-X"
|
||||
}
|
||||
durationValue.setText(displayValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Partie OpenAI : enregistrement de la clé en dur.
|
||||
// Récupérer une instance des SharedPreferences
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
final SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
// Récupérer la valeur enregistrée dans les SharedPreferences
|
||||
String savedText = sharedPreferences.getString("savedText", "");
|
||||
editTextKeyGPT.setText(savedText);
|
||||
|
||||
// Enregistrer le contenu de l'EditText lorsque l'utilisateur modifie le texte
|
||||
editTextKeyGPT.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// Enregistrer le texte dans les SharedPreferences
|
||||
editor.putString("savedText", editTextKeyGPT.getText().toString());
|
||||
editor.apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onClickButtonTestAPI(View view) {
|
||||
String apiKey = editTextKeyGPT.getText().toString();
|
||||
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
Toast.makeText(this, "Veuillez entrer une clé API", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer un client OkHttpClient pour effectuer la requête
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
// Déterminer l'URL, le modèle et le format selon le provider sélectionné
|
||||
String testUrl;
|
||||
String testModel;
|
||||
String jsonBody;
|
||||
boolean isAnthropicFormat = (selectedProvider == OpenAIService.AIProvider.ZAI);
|
||||
|
||||
switch (selectedProvider) {
|
||||
case OPENROUTER:
|
||||
testUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
testModel = "openai/gpt-3.5-turbo";
|
||||
// Format OpenAI
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case ZAI:
|
||||
testUrl = "https://api.z.ai/v1/messages";
|
||||
testModel = "claude-3-5-sonnet";
|
||||
// Format Anthropic
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case OPENAI:
|
||||
default:
|
||||
testUrl = "https://api.openai.com/v1/chat/completions";
|
||||
testModel = "gpt-3.5-turbo";
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
}
|
||||
|
||||
// Construire la requête
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(testUrl)
|
||||
.addHeader("Content-Type", "application/json");
|
||||
|
||||
// Ajouter les headers selon le provider
|
||||
switch (selectedProvider) {
|
||||
case OPENAI:
|
||||
case OPENROUTER:
|
||||
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
|
||||
break;
|
||||
case ZAI:
|
||||
requestBuilder.addHeader("x-api-key", apiKey);
|
||||
requestBuilder.addHeader("anthropic-version", "2023-06-01");
|
||||
break;
|
||||
}
|
||||
|
||||
// Headers spécifiques pour OpenRouter
|
||||
if (selectedProvider == OpenAIService.AIProvider.OPENROUTER) {
|
||||
requestBuilder.addHeader("HTTP-Referer", "https://boidelo.app");
|
||||
requestBuilder.addHeader("X-Title", "Boidelo");
|
||||
}
|
||||
|
||||
Request request = requestBuilder
|
||||
.post(okhttp3.RequestBody.create(jsonBody, okhttp3.MediaType.parse("application/json")))
|
||||
.build();
|
||||
|
||||
// Exécuter la requête de test
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, IOException e) {
|
||||
String operation = "Test de connexion API " + selectedProvider.getDisplayName();
|
||||
String details = "Échec de connexion lors du test de l'API";
|
||||
ErrorHandler.logErrorOnly("BoideloClassicParamsActivity", operation + " - " + details, e);
|
||||
runOnUiThread(() -> {
|
||||
String userMessage = "Échec de connexion " + selectedProvider.getDisplayName() + " : " + e.getMessage();
|
||||
Toast.makeText(getApplicationContext(), userMessage, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Connexion " + selectedProvider.getDisplayName() + " réussie !",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Erreur " + selectedProvider.getDisplayName() + " (HTTP " + response.code() + ")",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
response.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise les vues de l'activité
|
||||
* Met à jour l'état de la carte OpenAI (visibilité et taille)
|
||||
* Quand l'IA est désactivée, cache tous les éléments sauf le titre et le switch
|
||||
*/
|
||||
private void initViews() {
|
||||
// TODO: Initialiser les vues pour les paramètres
|
||||
private void updateOpenAICardState(boolean isOpenAIEnabled) {
|
||||
// Activation/désactivation des vues en fonction de l'état du checkBox
|
||||
autoCompleteProvider.setEnabled(isOpenAIEnabled);
|
||||
// Pour le champ API key : on garde le layout activé pour le toggle password,
|
||||
// mais on désactive l'édition du texte
|
||||
editTextKeyGPT.setFocusable(isOpenAIEnabled);
|
||||
editTextKeyGPT.setFocusableInTouchMode(isOpenAIEnabled);
|
||||
editTextKeyGPT.setClickable(isOpenAIEnabled);
|
||||
editTextKeyGPT.setCursorVisible(isOpenAIEnabled);
|
||||
if (!isOpenAIEnabled) {
|
||||
editTextKeyGPT.clearFocus();
|
||||
}
|
||||
textViewRatioGen.setEnabled(isOpenAIEnabled);
|
||||
seekBar3.setEnabled(isOpenAIEnabled);
|
||||
buttonTestApi.setEnabled(isOpenAIEnabled);
|
||||
|
||||
// Cacher/montrer les éléments pour réduire la taille de la carte
|
||||
// Les éléments à cacher quand l'IA est désactivée (index 2 à 6 dans le LinearLayout)
|
||||
// Index: 0=titreLinearLayout, 1=textInputLayoutProvider, 2=textInputLayoutApiKey,
|
||||
// 3=ratioSeekBarLinearLayout, 4=buttonTestApi
|
||||
if (openaiContentLayout != null) {
|
||||
for (int i = 1; i < openaiContentLayout.getChildCount(); i++) {
|
||||
View child = openaiContentLayout.getChildAt(i);
|
||||
if (child != null) {
|
||||
child.setVisibility(isOpenAIEnabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure les écouteurs d'événements
|
||||
*/
|
||||
private void setupListeners() {
|
||||
// TODO: Configurer les listeners pour les changements de paramètres
|
||||
}
|
||||
public void onClickButtonStart(View view) {
|
||||
// Récupérer les paramètres de la partie
|
||||
int nombreQuestions = seekBar1.getProgress();
|
||||
int ajoutGorgees = seekBar2.getProgress();
|
||||
int ratioBddOpenAI = seekBar3.getProgress();
|
||||
int durationDefis = seekBarDuration.getProgress();
|
||||
boolean openAI = checkBoxGPT.isChecked();
|
||||
|
||||
/**
|
||||
* Charge les paramètres actuels depuis les préférences
|
||||
*/
|
||||
private void loadCurrentSettings() {
|
||||
// TODO: Charger les paramètres depuis SharedPreferences
|
||||
}
|
||||
// Récupérer les joueurs depuis l'intent
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
|
||||
/**
|
||||
* Sauvegarde les paramètres
|
||||
*/
|
||||
private void saveSettings() {
|
||||
// TODO: Sauvegarder les paramètres dans SharedPreferences
|
||||
if (toutlesjoueurs == null || toutlesjoueurs.isEmpty()) {
|
||||
Toast.makeText(this, "Erreur: Aucun joueur trouvé", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lancer l'activité BoideloClassicGameActivity avec les paramètres
|
||||
Intent intent = new Intent(this, BoideloClassicGameActivity.class);
|
||||
intent.putExtra("EXTRA_NOMBRE_QUESTIONS", nombreQuestions);
|
||||
intent.putExtra("EXTRA_AJOUT_GORGEE", ajoutGorgees);
|
||||
intent.putExtra("EXTRA_RATIO_OPENAI", ratioBddOpenAI);
|
||||
intent.putExtra("EXTRA_DURATION_DEFIS", durationDefis);
|
||||
intent.putExtra("EXTRA_OPENAI", openAI);
|
||||
intent.putExtra("EXTRA_KEY_OPENAI", editTextKeyGPT.getText().toString());
|
||||
intent.putExtra("EXTRA_AI_PROVIDER", selectedProvider.name());
|
||||
intent.putStringArrayListExtra("PLAYERS", toutlesjoueurs);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+14
-10
@@ -33,7 +33,7 @@ public class BoideloClassicSetupActivity extends AppCompatActivity {
|
||||
private MaterialButton startGameButton;
|
||||
private TextView playerCountText;
|
||||
private MaterialToolbar toolbar;
|
||||
|
||||
|
||||
private final List<String> playerNames = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
@@ -74,8 +74,8 @@ public class BoideloClassicSetupActivity extends AppCompatActivity {
|
||||
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
startGameButton.setOnClickListener(v -> startGame());
|
||||
|
||||
startGameButton.setOnClickListener(v -> goToParams());
|
||||
}
|
||||
|
||||
private void addPlayerRow() {
|
||||
@@ -155,17 +155,20 @@ public class BoideloClassicSetupActivity extends AppCompatActivity {
|
||||
int validPlayers = playersContainer.getChildCount();
|
||||
boolean canStart = validPlayers >= MIN_PLAYERS;
|
||||
startGameButton.setEnabled(canStart);
|
||||
startGameButton.setText(canStart ? "JOUER (" + validPlayers + ")" : "Ajoutez des joueurs");
|
||||
startGameButton.setText(canStart ? "PARAMÈTRES ET JEU (" + validPlayers + ")" : "Ajoutez des joueurs");
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
/**
|
||||
* Redirige vers l'écran des paramètres avant de lancer le jeu
|
||||
*/
|
||||
private void goToParams() {
|
||||
// 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();
|
||||
@@ -173,14 +176,15 @@ public class BoideloClassicSetupActivity extends AppCompatActivity {
|
||||
}
|
||||
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);
|
||||
|
||||
// Rediriger vers l'écran des paramètres
|
||||
Intent intent = new Intent(this, BoideloClassicParamsActivity.class);
|
||||
intent.putStringArrayListExtra("EXTRA_LIST_JOUEUR", validNames);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Logique du jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoGame {
|
||||
|
||||
private final List<PapelitoPlayer> players;
|
||||
private final List<PapelitoPlayer> alivePlayers;
|
||||
private final List<String> wordPairs;
|
||||
private String currentCivilWord;
|
||||
private String currentUndercoverWord;
|
||||
private int currentPlayerIndex;
|
||||
private final Random random;
|
||||
private GameState gameState;
|
||||
|
||||
public enum GameState {
|
||||
SETUP,
|
||||
DISCUSSION,
|
||||
VOTING,
|
||||
RESULT,
|
||||
GAME_OVER
|
||||
}
|
||||
|
||||
// Paires de mots pour le jeu (civil / undercover)
|
||||
private static final String[][] DEFAULT_WORD_PAIRS = {
|
||||
{"Pizza", "Burger"},
|
||||
{"Facebook", "Instagram"},
|
||||
{"Chat", "Chien"},
|
||||
{"Foot", "Basket"},
|
||||
{"Vin", "Bière"},
|
||||
{"Mer", "Montagne"},
|
||||
{"Avion", "Hélicoptère"},
|
||||
{"Piano", "Guitare"},
|
||||
{"Fromage", "Dessert"},
|
||||
{"École", "Fac"},
|
||||
{"Mariage", "Divorce"},
|
||||
{"Hôpital", "Cabinet"},
|
||||
{"Boulangerie", "Boucherie"},
|
||||
{"Zombie", "Vampire"},
|
||||
{"Pirate", "Ninja"},
|
||||
{"Cowboy", "Indien"},
|
||||
{"Fée", "Sorcière"},
|
||||
{"Robot", "Alien"},
|
||||
{"Dragon", "Licorne"}
|
||||
};
|
||||
|
||||
public PapelitoGame() {
|
||||
this.players = new ArrayList<>();
|
||||
this.alivePlayers = new ArrayList<>();
|
||||
this.wordPairs = new ArrayList<>();
|
||||
this.random = new Random();
|
||||
this.gameState = GameState.SETUP;
|
||||
|
||||
// Ajouter les paires de mots par défaut
|
||||
for (String[] pair : DEFAULT_WORD_PAIRS) {
|
||||
wordPairs.add(pair[0] + "|" + pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure une nouvelle partie
|
||||
*/
|
||||
public void setupGame(List<String> playerNames, int undercoverCount) {
|
||||
players.clear();
|
||||
|
||||
// Créer les joueurs
|
||||
for (String name : playerNames) {
|
||||
players.add(new PapelitoPlayer(name));
|
||||
}
|
||||
|
||||
// Vérifier qu'on a assez de joueurs
|
||||
if (undercoverCount >= players.size()) {
|
||||
throw new IllegalArgumentException("Il faut plus de joueurs que d'undercovers");
|
||||
}
|
||||
|
||||
// Choisir une paire de mots aléatoire
|
||||
String[] words = selectRandomWordPair();
|
||||
currentCivilWord = words[0];
|
||||
currentUndercoverWord = words[1];
|
||||
|
||||
// Assigner les rôles
|
||||
assignRoles(undercoverCount);
|
||||
|
||||
// Initialiser la liste des joueurs vivants
|
||||
alivePlayers.clear();
|
||||
alivePlayers.addAll(players);
|
||||
|
||||
gameState = GameState.DISCUSSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne une paire de mots aléatoire
|
||||
*/
|
||||
private String[] selectRandomWordPair() {
|
||||
String pair = wordPairs.get(random.nextInt(wordPairs.size()));
|
||||
String[] words = pair.split("\\|");
|
||||
if (words.length != 2) {
|
||||
throw new IllegalStateException("Invalid word pair format: " + pair);
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigne les rôles aux joueurs
|
||||
*/
|
||||
private void assignRoles(int undercoverCount) {
|
||||
if (players.isEmpty()) return;
|
||||
|
||||
// Mélanger les joueurs
|
||||
List<PapelitoPlayer> shuffled = new ArrayList<>(players);
|
||||
Collections.shuffle(shuffled);
|
||||
|
||||
// Assigner les undercovers
|
||||
for (int i = 0; i < undercoverCount; i++) {
|
||||
shuffled.get(i).setRole(PapelitoPlayer.Role.UNDERCOVER);
|
||||
shuffled.get(i).setSecretWord(currentUndercoverWord);
|
||||
}
|
||||
|
||||
// Le reste sont des civils
|
||||
for (int i = undercoverCount; i < shuffled.size(); i++) {
|
||||
shuffled.get(i).setRole(PapelitoPlayer.Role.CIVIL);
|
||||
shuffled.get(i).setSecretWord(currentCivilWord);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un vote contre un joueur
|
||||
*/
|
||||
public boolean vote(PapelitoPlayer voter, PapelitoPlayer votedPlayer) {
|
||||
if (!voter.isAlive() || !votedPlayer.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gameState.equals(GameState.VOTING)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier que le joueur n'a pas déjà voté
|
||||
// (pour simplifier, on ne track pas qui a voté, chaque joueur peut voter une fois)
|
||||
|
||||
votedPlayer.addVote();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Élimine le joueur avec le plus de votes
|
||||
* Retourne null si pas de votes ou en cas d'égalité
|
||||
*/
|
||||
public PapelitoPlayer eliminateMostVoted() {
|
||||
if (alivePlayers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PapelitoPlayer mostVoted = null;
|
||||
int maxVotes = -1;
|
||||
int playersWithMaxVotes = 0;
|
||||
|
||||
// Trouver le nombre maximum de votes
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.getVotesReceived() > maxVotes) {
|
||||
maxVotes = player.getVotesReceived();
|
||||
mostVoted = player;
|
||||
playersWithMaxVotes = 1;
|
||||
} else if (player.getVotesReceived() == maxVotes && maxVotes > 0) {
|
||||
// Égalité détectée
|
||||
playersWithMaxVotes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Si pas de votes ou égalité entre plusieurs joueurs, personne n'est éliminé
|
||||
if (maxVotes <= 0 || playersWithMaxVotes > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mostVoted != null) {
|
||||
mostVoted.eliminate();
|
||||
alivePlayers.remove(mostVoted);
|
||||
|
||||
// Révéler son rôle
|
||||
return mostVoted;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la partie est terminée
|
||||
*/
|
||||
public boolean checkGameOver() {
|
||||
int civilsCount = 0;
|
||||
int undercoversCount = 0;
|
||||
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isCivil()) {
|
||||
civilsCount++;
|
||||
} else if (player.isUndercover()) {
|
||||
undercoversCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Les civils gagnent s'il n'y a plus d'undercovers
|
||||
if (undercoversCount == 0) {
|
||||
gameState = GameState.GAME_OVER;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les undercovers gagnent s'ils sont en nombre égal ou supérieur aux civils
|
||||
// Explication: Si les undercovers sont égal ou plus nombreux, les civils ne peuvent plus gagner
|
||||
// car il n'y aurait plus assez de civils pour voter et éliminer tous les undercovers
|
||||
if (undercoversCount >= civilsCount) {
|
||||
gameState = GameState.GAME_OVER;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le joueur actuel
|
||||
*/
|
||||
public PapelitoPlayer getCurrentPlayer() {
|
||||
if (!alivePlayers.isEmpty()) {
|
||||
// Utilise alivePlayers pour les joueurs vivants
|
||||
if (currentPlayerIndex >= 0 && currentPlayerIndex < alivePlayers.size()) {
|
||||
return alivePlayers.get(currentPlayerIndex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe au joueur suivant
|
||||
*/
|
||||
public void nextPlayer() {
|
||||
if (!alivePlayers.isEmpty()) {
|
||||
currentPlayerIndex = (currentPlayerIndex + 1) % alivePlayers.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le nombre de civils vivants
|
||||
*/
|
||||
public int getAliveCivilsCount() {
|
||||
int count = 0;
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isCivil()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le nombre d'undercovers vivants
|
||||
*/
|
||||
public int getAliveUndercoversCount() {
|
||||
int count = 0;
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isUndercover()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les gagnants
|
||||
*/
|
||||
public PapelitoPlayer.Role getWinningTeam() {
|
||||
int civilsCount = getAliveCivilsCount();
|
||||
int undercoversCount = getAliveUndercoversCount();
|
||||
|
||||
if (undercoversCount == 0) {
|
||||
return PapelitoPlayer.Role.CIVIL;
|
||||
} else if (undercoversCount >= civilsCount) {
|
||||
return PapelitoPlayer.Role.UNDERCOVER;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise les votes pour un nouveau tour
|
||||
*/
|
||||
public void resetVotes() {
|
||||
for (PapelitoPlayer player : players) {
|
||||
player.resetVotes();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public List<PapelitoPlayer> getPlayers() {
|
||||
return new ArrayList<>(players);
|
||||
}
|
||||
|
||||
public List<PapelitoPlayer> getAlivePlayers() {
|
||||
return new ArrayList<>(alivePlayers);
|
||||
}
|
||||
|
||||
public String getCurrentCivilWord() {
|
||||
return currentCivilWord;
|
||||
}
|
||||
|
||||
public String getCurrentUndercoverWord() {
|
||||
return currentUndercoverWord;
|
||||
}
|
||||
|
||||
public GameState getGameState() {
|
||||
return gameState;
|
||||
}
|
||||
|
||||
public void setGameState(GameState state) {
|
||||
// Validation des transitions légales entre états
|
||||
GameState current = this.gameState;
|
||||
|
||||
// Transitions légales autorisées
|
||||
if (current == GameState.SETUP && state != GameState.DISCUSSION && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from SETUP to " + state);
|
||||
}
|
||||
if (current == GameState.DISCUSSION && state != GameState.VOTING && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from DISCUSSION to " + state);
|
||||
}
|
||||
if (current == GameState.VOTING && state != GameState.RESULT && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from VOTING to " + state);
|
||||
}
|
||||
if (current == GameState.RESULT && state != GameState.DISCUSSION && state != GameState.GAME_OVER && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from RESULT to " + state);
|
||||
}
|
||||
if (current == GameState.GAME_OVER && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from GAME_OVER to " + state);
|
||||
}
|
||||
|
||||
this.gameState = state;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
players.clear();
|
||||
alivePlayers.clear();
|
||||
currentPlayerIndex = 0;
|
||||
gameState = GameState.SETUP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.content.Intent;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Activité principale du jeu Papelito (Undercover)
|
||||
*
|
||||
* Gère le déroulement complet du jeu :
|
||||
* - Affichage du mot secret à chaque joueur
|
||||
* - Phase de discussion avec timer
|
||||
* - Phase de vote
|
||||
* - Affichage des résultats
|
||||
* - Condition de victoire
|
||||
*/
|
||||
public class PapelitoGameActivity extends AppCompatActivity {
|
||||
|
||||
// UI Components
|
||||
private MaterialToolbar toolbar;
|
||||
private TextView phaseTextView;
|
||||
private TextView infoTextView;
|
||||
private TextView timerTextView;
|
||||
private MaterialCardView wordCard;
|
||||
private TextView wordTextView;
|
||||
private TextView currentWordPlayerTextView;
|
||||
private MaterialButton showWordButton;
|
||||
private MaterialButton startDiscussionButton;
|
||||
private MaterialButton startVotingButton;
|
||||
private MaterialButton nextPlayerButton;
|
||||
private MaterialButton endGameButton;
|
||||
private View mainContent;
|
||||
|
||||
// Game Logic
|
||||
private PapelitoGame game;
|
||||
private List<String> playerNames;
|
||||
private int undercoverCount;
|
||||
private int discussionTimeSeconds;
|
||||
private CountDownTimer discussionTimer;
|
||||
private int currentPlayerViewIndex;
|
||||
private boolean hasShownWordToPlayer;
|
||||
private List<String> playersWhoVoted;
|
||||
|
||||
// Constants
|
||||
private static final int DEFAULT_DISCUSSION_TIME = 60; // 60 secondes
|
||||
private static final int DEFAULT_UNDERCOVER_COUNT = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_game);
|
||||
|
||||
// Get intent data with validation
|
||||
playerNames = getIntent().getStringArrayListExtra("PLAYERS");
|
||||
undercoverCount = getIntent().getIntExtra("UNDERCOVER_COUNT", DEFAULT_UNDERCOVER_COUNT);
|
||||
discussionTimeSeconds = getIntent().getIntExtra("DISCUSSION_TIME", DEFAULT_DISCUSSION_TIME);
|
||||
|
||||
// Validate all intent extras
|
||||
if (playerNames == null || playerNames.isEmpty()) {
|
||||
Toast.makeText(this, "Erreur: Aucun joueur spécifié pour la partie", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (undercoverCount <= 0) {
|
||||
Toast.makeText(this, "Erreur: Le nombre d'undercovers doit être au moins de 1", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (undercoverCount >= playerNames.size()) {
|
||||
Toast.makeText(this, "Erreur: Il doit y avoir plus de joueurs que d'undercovers", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (discussionTimeSeconds <= 0) {
|
||||
Toast.makeText(this, "Erreur: La durée de discussion doit être positive", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupGame();
|
||||
setupListeners();
|
||||
|
||||
// Commencer par afficher le mot au premier joueur
|
||||
startWordRevealPhase();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
phaseTextView = findViewById(R.id.phaseTextView);
|
||||
infoTextView = findViewById(R.id.infoTextView);
|
||||
timerTextView = findViewById(R.id.timerTextView);
|
||||
wordCard = findViewById(R.id.wordCard);
|
||||
wordTextView = findViewById(R.id.wordTextView);
|
||||
currentWordPlayerTextView = findViewById(R.id.currentWordPlayerTextView);
|
||||
showWordButton = findViewById(R.id.showWordButton);
|
||||
startDiscussionButton = findViewById(R.id.startDiscussionButton);
|
||||
startVotingButton = findViewById(R.id.startVotingButton);
|
||||
nextPlayerButton = findViewById(R.id.nextPlayerButton);
|
||||
endGameButton = findViewById(R.id.endGameButton);
|
||||
mainContent = findViewById(R.id.mainContent);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> showExitConfirmationDialog());
|
||||
}
|
||||
|
||||
private void setupGame() {
|
||||
game = new PapelitoGame();
|
||||
game.setupGame(playerNames, undercoverCount);
|
||||
currentPlayerViewIndex = 0;
|
||||
hasShownWordToPlayer = false;
|
||||
playersWhoVoted = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
showWordButton.setOnClickListener(v -> showSecretWord());
|
||||
nextPlayerButton.setOnClickListener(v -> moveToNextPlayer());
|
||||
startDiscussionButton.setOnClickListener(v -> startDiscussionPhase());
|
||||
startVotingButton.setOnClickListener(v -> startVotingPhase());
|
||||
endGameButton.setOnClickListener(v -> showGameOverDialog());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 1: RÉVÉLATION DES MOTS
|
||||
// ============================================================
|
||||
|
||||
private void startWordRevealPhase() {
|
||||
game.setGameState(PapelitoGame.GameState.SETUP);
|
||||
// Reset game's currentPlayerIndex to prevent wrong player start
|
||||
currentPlayerViewIndex = 0;
|
||||
hasShownWordToPlayer = false;
|
||||
|
||||
updateUIForWordReveal();
|
||||
showCurrentPlayerPrompt();
|
||||
}
|
||||
|
||||
private void updateUIForWordReveal() {
|
||||
phaseTextView.setText("Phase 1: Révélation des mots");
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
|
||||
// Cacher la carte du mot
|
||||
wordCard.setVisibility(View.GONE);
|
||||
wordTextView.setText("");
|
||||
|
||||
// Afficher le bouton pour montrer le mot
|
||||
showWordButton.setVisibility(View.VISIBLE);
|
||||
nextPlayerButton.setVisibility(View.VISIBLE);
|
||||
|
||||
// Cacher les boutons de phase suivante
|
||||
startDiscussionButton.setVisibility(View.GONE);
|
||||
startVotingButton.setVisibility(View.GONE);
|
||||
endGameButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showCurrentPlayerPrompt() {
|
||||
PapelitoPlayer player = game.getPlayers().get(currentPlayerViewIndex);
|
||||
currentWordPlayerTextView.setText(
|
||||
String.format("Tour de %s", player.getName())
|
||||
);
|
||||
infoTextView.setText(
|
||||
"Passe le téléphone à " + player.getName() + "\n\n" +
|
||||
"Appuie sur 'Voir mon mot' pour découvrir ton mot secret."
|
||||
);
|
||||
hasShownWordToPlayer = false;
|
||||
showWordButton.setEnabled(true);
|
||||
}
|
||||
|
||||
private void showSecretWord() {
|
||||
PapelitoPlayer player = game.getPlayers().get(currentPlayerViewIndex);
|
||||
String secretWord = player.getSecretWord();
|
||||
|
||||
wordTextView.setText(secretWord);
|
||||
wordCard.setVisibility(View.VISIBLE);
|
||||
showWordButton.setEnabled(false);
|
||||
hasShownWordToPlayer = true;
|
||||
|
||||
infoTextView.setText(
|
||||
"Ton mot est: " + secretWord + "\n\n" +
|
||||
"Mémorise-le bien et appuie sur 'Joueur suivant' " +
|
||||
"quand tu es prêt."
|
||||
);
|
||||
}
|
||||
|
||||
private void moveToNextPlayer() {
|
||||
if (!hasShownWordToPlayer) {
|
||||
Toast.makeText(this,
|
||||
"Tu dois d'abord voir ton mot!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
currentPlayerViewIndex++;
|
||||
|
||||
if (currentPlayerViewIndex >= game.getPlayers().size()) {
|
||||
// Tous les joueurs ont vu leur mot
|
||||
showReadyForDiscussionDialog();
|
||||
} else {
|
||||
showCurrentPlayerPrompt();
|
||||
wordCard.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void showReadyForDiscussionDialog() {
|
||||
currentWordPlayerTextView.setText("Prêts!");
|
||||
infoTextView.setText(
|
||||
"Tous les joueurs ont vu leur mot.\n\n" +
|
||||
"Préparez-vous pour la phase de discussion!"
|
||||
);
|
||||
wordCard.setVisibility(View.GONE);
|
||||
showWordButton.setVisibility(View.GONE);
|
||||
nextPlayerButton.setVisibility(View.GONE);
|
||||
startDiscussionButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 2: DISCUSSION
|
||||
// ============================================================
|
||||
|
||||
private void startDiscussionPhase() {
|
||||
game.setGameState(PapelitoGame.GameState.DISCUSSION);
|
||||
updateUIForDiscussion();
|
||||
startDiscussionTimer();
|
||||
}
|
||||
|
||||
private void updateUIForDiscussion() {
|
||||
phaseTextView.setText("Phase 2: Discussion");
|
||||
currentWordPlayerTextView.setText("Discutez!");
|
||||
|
||||
wordCard.setVisibility(View.GONE);
|
||||
showWordButton.setVisibility(View.GONE);
|
||||
nextPlayerButton.setVisibility(View.GONE);
|
||||
startDiscussionButton.setVisibility(View.GONE);
|
||||
|
||||
timerTextView.setVisibility(View.VISIBLE);
|
||||
startVotingButton.setVisibility(View.VISIBLE);
|
||||
|
||||
infoTextView.setText(
|
||||
"Chaque joueur décrit son mot tour à tour.\n\n" +
|
||||
"Les Undercovers doivent essayer de deviner le mot des civils " +
|
||||
"sans se faire repérer.\n\n" +
|
||||
"Les civils doivent essayer d'identifier les Undercovers."
|
||||
);
|
||||
}
|
||||
|
||||
private void startDiscussionTimer() {
|
||||
discussionTimer = new CountDownTimer(
|
||||
discussionTimeSeconds * 1000L, 1000
|
||||
) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
long minutes = millisUntilFinished / 60000;
|
||||
long seconds = (millisUntilFinished % 60000) / 1000;
|
||||
timerTextView.setText(
|
||||
String.format(Locale.getDefault(),
|
||||
"%d:%02d", minutes, seconds)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
timerTextView.setText("0:00");
|
||||
Toast.makeText(PapelitoGameActivity.this,
|
||||
"Temps écoulé! Passez au vote.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 3: VOTE
|
||||
// ============================================================
|
||||
|
||||
private void startVotingPhase() {
|
||||
// Arrêter le timer de discussion
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
}
|
||||
|
||||
game.setGameState(PapelitoGame.GameState.VOTING);
|
||||
game.resetVotes();
|
||||
playersWhoVoted.clear();
|
||||
|
||||
updateUIForVoting();
|
||||
showVotingDialog();
|
||||
}
|
||||
|
||||
private void updateUIForVoting() {
|
||||
phaseTextView.setText("Phase 3: Vote");
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
startVotingButton.setVisibility(View.GONE);
|
||||
endGameButton.setVisibility(View.VISIBLE);
|
||||
|
||||
infoTextView.setText(
|
||||
"Chaque joueur vivant vote pour éliminer quelqu'un.\n\n" +
|
||||
"Le joueur avec le plus de votes est éliminé."
|
||||
);
|
||||
}
|
||||
|
||||
private void showVotingDialog() {
|
||||
List<PapelitoPlayer> alivePlayers = game.getAlivePlayers();
|
||||
|
||||
if (alivePlayers.isEmpty()) {
|
||||
checkGameEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Vote pour éliminer");
|
||||
|
||||
// Créer le layout personnalisé
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(50, 40, 50, 10);
|
||||
|
||||
TextView label = new TextView(this);
|
||||
label.setText("Qui voulez-vous éliminer?");
|
||||
label.setPadding(0, 0, 0, 20);
|
||||
layout.addView(label);
|
||||
|
||||
// Créer une grille de boutons pour chaque joueur vivant
|
||||
GridLayout playerGrid = new GridLayout(this);
|
||||
playerGrid.setColumnCount(2);
|
||||
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
MaterialButton playerButton = new MaterialButton(this);
|
||||
playerButton.setText(player.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 -> {
|
||||
recordVote(player);
|
||||
});
|
||||
|
||||
playerGrid.addView(playerButton);
|
||||
}
|
||||
|
||||
layout.addView(playerGrid);
|
||||
|
||||
builder.setView(layout);
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void recordVote(PapelitoPlayer votedPlayer) {
|
||||
if (playersWhoVoted.size() >= game.getAlivePlayers().size()) {
|
||||
// Tous ont voté
|
||||
return;
|
||||
}
|
||||
|
||||
// Enregistrer le vote (simplifié : on ne track pas QUI a voté)
|
||||
votedPlayer.addVote();
|
||||
playersWhoVoted.add("vote");
|
||||
|
||||
Toast.makeText(this,
|
||||
"Vote enregistré pour " + votedPlayer.getName(),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Vérifier si tous les joueurs vivants ont voté
|
||||
if (playersWhoVoted.size() >= game.getAlivePlayers().size()) {
|
||||
// Tous les votes sont en, afficher le résultat
|
||||
showVotingResult();
|
||||
} else {
|
||||
// Continuer avec le prochain voteur
|
||||
showNextVoterDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNextVoterDialog() {
|
||||
int votesRemaining = game.getAlivePlayers().size() - playersWhoVoted.size();
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Vote en cours");
|
||||
builder.setMessage(
|
||||
"Joueurs ayant voté: " + playersWhoVoted.size() + " / " +
|
||||
game.getAlivePlayers().size() + "\n\n" +
|
||||
"Passe le téléphone au prochain joueur."
|
||||
);
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
showVotingDialog();
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showVotingResult() {
|
||||
PapelitoPlayer eliminated = game.eliminateMostVoted();
|
||||
|
||||
if (eliminated == null) {
|
||||
// Personne n'a été éliminé (pas de votes ou égalité)
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Vote nul!");
|
||||
builder.setMessage(
|
||||
"Aucun joueur n'a été éliminé.\n\n" +
|
||||
"Soit il n'y avait pas de votes, soit il y a une égalité.\n\n" +
|
||||
"La partie continue!"
|
||||
);
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
checkGameEnd();
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher le résultat de l'élimination
|
||||
showEliminationResult(eliminated);
|
||||
}
|
||||
|
||||
private void showEliminationResult(PapelitoPlayer eliminated) {
|
||||
game.setGameState(PapelitoGame.GameState.RESULT);
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Joueur éliminé!");
|
||||
|
||||
String message = String.format(
|
||||
"%s a été éliminé!\n\n" +
|
||||
"Son rôle était: %s\n\n" +
|
||||
"Son mot était: %s",
|
||||
eliminated.getName(),
|
||||
eliminated.getRole().getDisplayName(),
|
||||
eliminated.getSecretWord()
|
||||
);
|
||||
|
||||
builder.setMessage(message);
|
||||
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
checkGameEnd();
|
||||
});
|
||||
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// VÉRIFICATION DE FIN DE JEU
|
||||
// ============================================================
|
||||
|
||||
private void checkGameEnd() {
|
||||
boolean gameOver = game.checkGameOver();
|
||||
|
||||
if (gameOver) {
|
||||
showGameOverDialog();
|
||||
} else {
|
||||
// Continuer avec un nouveau tour
|
||||
showNextRoundDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNextRoundDialog() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Nouveau tour");
|
||||
|
||||
StringBuilder status = new StringBuilder();
|
||||
status.append("Joueurs vivants:\n\n");
|
||||
|
||||
for (PapelitoPlayer player : game.getAlivePlayers()) {
|
||||
status.append("• ").append(player.getName()).append("\n");
|
||||
}
|
||||
|
||||
status.append("\nCivils: ").append(game.getAliveCivilsCount());
|
||||
status.append("\nUndercovers: ").append(game.getAliveUndercoversCount());
|
||||
|
||||
builder.setMessage(status.toString());
|
||||
|
||||
builder.setPositiveButton("Commencer", (dialog, which) -> {
|
||||
startWordRevealPhase();
|
||||
});
|
||||
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showGameOverDialog() {
|
||||
game.setGameState(PapelitoGame.GameState.GAME_OVER);
|
||||
|
||||
// Lancer l'activité de résultat
|
||||
Intent intent = new Intent(this, PapelitoResultActivity.class);
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_PLAYERS, new ArrayList<>(game.getPlayers()));
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_WINNING_TEAM, game.getWinningTeam());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_CIVIL_WORD, game.getCurrentCivilWord());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_UNDERCOVER_WORD, game.getCurrentUndercoverWord());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_TOTAL_ROUNDS, playersWhoVoted.size());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DIVERS
|
||||
// ============================================================
|
||||
|
||||
private void showExitConfirmationDialog() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Quitter la partie?");
|
||||
builder.setMessage(
|
||||
"La partie sera perdue si vous quittez.\n\n" +
|
||||
"Voulez-vous vraiment quitter?"
|
||||
);
|
||||
|
||||
builder.setPositiveButton("Quitter", (dialog, which) -> {
|
||||
finish();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Continuer", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
discussionTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
showExitConfirmationDialog();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
/**
|
||||
* Représente un joueur du jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoPlayer implements java.io.Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String name;
|
||||
private String secretWord;
|
||||
private Role role;
|
||||
private boolean isAlive;
|
||||
private int votesReceived;
|
||||
|
||||
public enum Role {
|
||||
CIVIL("Civil"),
|
||||
UNDERCOVER("Undercover"),
|
||||
MR_WHITE("Mr. White");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
Role(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
||||
public PapelitoPlayer(String name) {
|
||||
this.name = name;
|
||||
this.isAlive = true;
|
||||
this.votesReceived = 0;
|
||||
this.role = null; // Sera assigné au début du jeu
|
||||
}
|
||||
|
||||
public PapelitoPlayer(String name, Role role, String secretWord) {
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
this.secretWord = secretWord;
|
||||
this.isAlive = true;
|
||||
this.votesReceived = 0;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getSecretWord() {
|
||||
return secretWord;
|
||||
}
|
||||
|
||||
public void setSecretWord(String secretWord) {
|
||||
this.secretWord = secretWord;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
/**
|
||||
* Retourne le rôle du joueur.
|
||||
* @return le rôle du joueur, peut être null si non assigné
|
||||
*/
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return isAlive;
|
||||
}
|
||||
|
||||
public void setAlive(boolean alive) {
|
||||
isAlive = alive;
|
||||
}
|
||||
|
||||
public void eliminate() {
|
||||
this.isAlive = false;
|
||||
}
|
||||
|
||||
public int getVotesReceived() {
|
||||
return votesReceived;
|
||||
}
|
||||
|
||||
public void addVote() {
|
||||
this.votesReceived++;
|
||||
}
|
||||
|
||||
public void resetVotes() {
|
||||
this.votesReceived = 0;
|
||||
}
|
||||
|
||||
public boolean isMrWhite() {
|
||||
return role != null && role == Role.MR_WHITE;
|
||||
}
|
||||
|
||||
public boolean isUndercover() {
|
||||
return role != null && role == Role.UNDERCOVER;
|
||||
}
|
||||
|
||||
public boolean isCivil() {
|
||||
return role != null && role == Role.CIVIL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (" + (role != null ? role.getDisplayName() : "?") + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.hub.GameSelectionActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Activité de fin de partie Papelito
|
||||
* Affiche les résultats, révèle tous les rôles et permet de rejouer
|
||||
*/
|
||||
public class PapelitoResultActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_PLAYERS = "extra_players";
|
||||
public static final String EXTRA_WINNING_TEAM = "extra_winning_team";
|
||||
public static final String EXTRA_CIVIL_WORD = "extra_civil_word";
|
||||
public static final String EXTRA_UNDERCOVER_WORD = "extra_undercover_word";
|
||||
public static final String EXTRA_TOTAL_ROUNDS = "extra_total_rounds";
|
||||
|
||||
private ArrayList<PapelitoPlayer> players;
|
||||
private PapelitoPlayer.Role winningTeam;
|
||||
private String civilWord;
|
||||
private String undercoverWord;
|
||||
private int totalRounds;
|
||||
|
||||
private TextView textViewWinner;
|
||||
private TextView textViewWords;
|
||||
private TextView textViewRounds;
|
||||
private RecyclerView recyclerViewResults;
|
||||
private Button buttonNewGame;
|
||||
private Button buttonHome;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_result);
|
||||
|
||||
// Récupérer les données
|
||||
players = (ArrayList<PapelitoPlayer>) getIntent().getSerializableExtra(EXTRA_PLAYERS);
|
||||
winningTeam = (PapelitoPlayer.Role) getIntent().getSerializableExtra(EXTRA_WINNING_TEAM);
|
||||
civilWord = getIntent().getStringExtra(EXTRA_CIVIL_WORD);
|
||||
undercoverWord = getIntent().getStringExtra(EXTRA_UNDERCOVER_WORD);
|
||||
totalRounds = getIntent().getIntExtra(EXTRA_TOTAL_ROUNDS, 0);
|
||||
|
||||
// Add validation
|
||||
if (players == null || players.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialiser les vues
|
||||
initViews();
|
||||
|
||||
// Afficher les résultats
|
||||
displayResults();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
textViewWinner = findViewById(R.id.winnerTextView);
|
||||
textViewWords = findViewById(R.id.undercoverWordTextView);
|
||||
textViewRounds = findViewById(R.id.roundsTextView);
|
||||
recyclerViewResults = findViewById(R.id.rolesRecyclerView);
|
||||
buttonNewGame = findViewById(R.id.newGameButton);
|
||||
buttonHome = findViewById(R.id.homeButton);
|
||||
|
||||
// Configurer le RecyclerView
|
||||
recyclerViewResults.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
// Bouton Nouvelle Partie
|
||||
buttonNewGame.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, PapelitoSetupActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
|
||||
// Bouton Retour Hub
|
||||
buttonHome.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void displayResults() {
|
||||
// Afficher l'équipe gagnante
|
||||
if (winningTeam != null) {
|
||||
String winnerText;
|
||||
int backgroundColor;
|
||||
|
||||
if (winningTeam == PapelitoPlayer.Role.CIVIL) {
|
||||
winnerText = "🎉 LES CIVILS ONT GAGNÉ !";
|
||||
backgroundColor = getColor(R.color.civil_bg);
|
||||
} else {
|
||||
winnerText = "🔴 LES UNDERCOVERS ONT GAGNÉ !";
|
||||
backgroundColor = getColor(R.color.undercover_bg);
|
||||
}
|
||||
|
||||
textViewWinner.setText(winnerText);
|
||||
findViewById(R.id.winnerCard).setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
// Afficher les mots (civilWordTextView et undercoverWordTextView)
|
||||
TextView civilWordView = findViewById(R.id.civilWordTextView);
|
||||
TextView undercoverWordView = findViewById(R.id.undercoverWordTextView);
|
||||
|
||||
if (civilWordView != null) {
|
||||
civilWordView.setText("Mot Civil: " + (civilWord != null ? civilWord : "Non défini"));
|
||||
}
|
||||
if (undercoverWordView != null) {
|
||||
undercoverWordView.setText("Mot Undercover: " + (undercoverWord != null ? undercoverWord : "Non défini"));
|
||||
}
|
||||
|
||||
// Afficher le nombre de tours
|
||||
String roundsText = "Nombre de manches: " + totalRounds;
|
||||
textViewRounds.setText(roundsText);
|
||||
|
||||
// Configurer l'adaptateur pour afficher tous les joueurs
|
||||
PapelitoResultAdapter adapter = new PapelitoResultAdapter(players, civilWord, undercoverWord);
|
||||
recyclerViewResults.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Adaptateur pour afficher les résultats finaux de tous les joueurs
|
||||
*/
|
||||
public class PapelitoResultAdapter extends RecyclerView.Adapter<PapelitoResultAdapter.ResultViewHolder> {
|
||||
|
||||
private final ArrayList<PapelitoPlayer> players;
|
||||
private final String civilWord;
|
||||
private final String undercoverWord;
|
||||
|
||||
public PapelitoResultAdapter(ArrayList<PapelitoPlayer> players, String civilWord, String undercoverWord) {
|
||||
this.players = players;
|
||||
this.civilWord = civilWord;
|
||||
this.undercoverWord = undercoverWord;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ResultViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_papelito_result, parent, false);
|
||||
return new ResultViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ResultViewHolder holder, int position) {
|
||||
PapelitoPlayer player = players.get(position);
|
||||
|
||||
// Nom du joueur
|
||||
holder.textViewPlayerName.setText(player.getName());
|
||||
|
||||
// Rôle avec vérification null
|
||||
if (player.getRole() != null) {
|
||||
holder.textViewRole.setText(player.getRole().getDisplayName());
|
||||
} else {
|
||||
holder.textViewRole.setText("?");
|
||||
}
|
||||
|
||||
// Statut (éliminé ou survivant)
|
||||
if (player.isAlive()) {
|
||||
holder.textViewStatus.setText("✅ Vivant");
|
||||
holder.textViewStatus.setTextColor(
|
||||
holder.itemView.getContext().getColor(R.color.success_green)
|
||||
);
|
||||
} else {
|
||||
holder.textViewStatus.setText("❌ Éliminé");
|
||||
holder.textViewStatus.setTextColor(
|
||||
holder.itemView.getContext().getColor(R.color.error_red)
|
||||
);
|
||||
}
|
||||
|
||||
// Avatar avec première lettre (vérification null et bounds)
|
||||
String name = player.getName();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
holder.playerAvatar.setText(name.substring(0, 1).toUpperCase());
|
||||
} else {
|
||||
holder.playerAvatar.setText("?");
|
||||
}
|
||||
|
||||
// Couleur de fond selon le rôle
|
||||
int backgroundColor;
|
||||
int roleColor;
|
||||
if (player.isCivil()) {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.civil_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.civil_bg);
|
||||
} else if (player.isUndercover()) {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.undercover_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.undercover_bg);
|
||||
} else {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.mr_white_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.mr_white_bg);
|
||||
}
|
||||
|
||||
// Définir la couleur de fond de la carte
|
||||
if (holder.cardView != null) {
|
||||
holder.cardView.setCardBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
// Définir la couleur du texte du rôle
|
||||
holder.textViewRole.setTextColor(roleColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return players.size();
|
||||
}
|
||||
|
||||
static class ResultViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView playerAvatar;
|
||||
TextView textViewPlayerName;
|
||||
TextView textViewRole;
|
||||
TextView textViewStatus;
|
||||
com.google.android.material.card.MaterialCardView cardView;
|
||||
|
||||
public ResultViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
cardView = (com.google.android.material.card.MaterialCardView) itemView;
|
||||
playerAvatar = itemView.findViewById(R.id.playerAvatarTextView);
|
||||
textViewPlayerName = itemView.findViewById(R.id.playerNameTextView);
|
||||
textViewRole = itemView.findViewById(R.id.playerRoleTextView);
|
||||
textViewStatus = itemView.findViewById(R.id.playerStatusBadge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
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.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.materialswitch.MaterialSwitch;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Activity de configuration pour le jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoSetupActivity extends AppCompatActivity {
|
||||
|
||||
private static final int MIN_PLAYERS = 3;
|
||||
private static final int MAX_PLAYERS = 12;
|
||||
private static final int MIN_UNDERCOVERS = 1;
|
||||
private static final int MAX_UNDERCOVERS = 3;
|
||||
|
||||
private LinearLayout playersContainer;
|
||||
private MaterialButton addPlayerButton;
|
||||
private MaterialButton startGameButton;
|
||||
private SeekBar undercoverSeekBar;
|
||||
private TextView undercoverText;
|
||||
private MaterialSwitch mrWhiteSwitch;
|
||||
private MaterialToolbar toolbar;
|
||||
|
||||
private static final int DEFAULT_DISCUSSION_TIME_SECONDS = 120; // 2 minutes par défaut
|
||||
private final List<String> playerNames = new ArrayList<>();
|
||||
private int undercoverCount = 1;
|
||||
private boolean mrWhiteEnabled = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_setup);
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupListeners();
|
||||
|
||||
// Ajouter 3 joueurs par défaut
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
|
||||
updateUndercoverText();
|
||||
updatePlayerNames();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
playersContainer = findViewById(R.id.playersContainer);
|
||||
addPlayerButton = findViewById(R.id.addPlayerButton);
|
||||
startGameButton = findViewById(R.id.startGameButton);
|
||||
undercoverSeekBar = findViewById(R.id.undercoverSeekBar);
|
||||
undercoverText = findViewById(R.id.undercoverText);
|
||||
mrWhiteSwitch = findViewById(R.id.mrWhiteSwitch);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
undercoverSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
undercoverCount = progress + 1; // +1 car le SeekBar commence à 0
|
||||
updateUndercoverText();
|
||||
updateMaxUndercoverLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
|
||||
mrWhiteSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
mrWhiteEnabled = isChecked;
|
||||
if (isChecked) {
|
||||
// Vérifier qu'on a assez de joueurs pour Mr White
|
||||
int requiredPlayers = undercoverCount + 2; // Au moins 2 civils
|
||||
if (playerNames.size() < requiredPlayers) {
|
||||
Toast.makeText(this,
|
||||
"Mr White nécessite au moins " + requiredPlayers + " joueurs",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
mrWhiteSwitch.setChecked(false);
|
||||
mrWhiteEnabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
updateMaxUndercoverLimit();
|
||||
} 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();
|
||||
updateMaxUndercoverLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la limite maximale d'undercovers selon le nombre de joueurs
|
||||
* Il faut toujours au moins 2 civils (plus Mr White si activé)
|
||||
*/
|
||||
private void updateMaxUndercoverLimit() {
|
||||
int playerCount = playerNames.size();
|
||||
int maxAllowed;
|
||||
|
||||
if (mrWhiteEnabled) {
|
||||
// Avec Mr White: max = joueurs - 2 (Mr White + 1 civil minimum)
|
||||
maxAllowed = Math.max(MIN_UNDERCOVERS, playerCount - 2);
|
||||
} else {
|
||||
// Sans Mr White: max = joueurs - 2 (2 civils minimum)
|
||||
maxAllowed = Math.max(MIN_UNDERCOVERS, playerCount - 2);
|
||||
}
|
||||
|
||||
// Ajuster si nécessaire
|
||||
if (undercoverCount > maxAllowed) {
|
||||
undercoverCount = maxAllowed;
|
||||
undercoverSeekBar.setProgress(undercoverCount - 1);
|
||||
updateUndercoverText();
|
||||
}
|
||||
|
||||
// Mettre à jour le max du SeekBar
|
||||
int seekBarMax = Math.min(MAX_UNDERCOVERS, maxAllowed);
|
||||
undercoverSeekBar.setMax(seekBarMax - 1); // -1 car le SeekBar commence à 0
|
||||
}
|
||||
|
||||
private void updateUndercoverText() {
|
||||
String text = undercoverCount + " undercover" + (undercoverCount > 1 ? "s" : "");
|
||||
undercoverText.setText(text);
|
||||
}
|
||||
|
||||
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 startGame() {
|
||||
updatePlayerNames();
|
||||
|
||||
if (playerNames.size() < MIN_PLAYERS) {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier qu'on a assez de joueurs pour la configuration
|
||||
int requiredPlayers = undercoverCount + 2; // Au moins 2 civils
|
||||
if (mrWhiteEnabled) {
|
||||
requiredPlayers++; // +1 pour Mr White
|
||||
}
|
||||
|
||||
if (playerNames.size() < requiredPlayers) {
|
||||
Toast.makeText(this,
|
||||
"Configuration invalide: il faut au moins " + requiredPlayers + " joueurs",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lancer l'activité de jeu
|
||||
Intent intent = new Intent(this, PapelitoGameActivity.class);
|
||||
intent.putStringArrayListExtra("PLAYERS", new ArrayList<>(playerNames));
|
||||
intent.putExtra("UNDERCOVER_COUNT", undercoverCount);
|
||||
intent.putExtra("MR_WHITE_ENABLED", mrWhiteEnabled);
|
||||
intent.putExtra("DISCUSSION_TIME", DEFAULT_DISCUSSION_TIME_SECONDS);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ 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.games.papelito.PapelitoSetupActivity;
|
||||
import com.example.boidelov3.hub.adapter.GameAdapter;
|
||||
import com.example.boidelov3.hub.model.GameInfo;
|
||||
import java.util.ArrayList;
|
||||
@@ -65,13 +66,13 @@ public class GameSelectionActivity extends AppCompatActivity implements GameAdap
|
||||
true // Available now
|
||||
));
|
||||
|
||||
// Undercover - Jeu de déduction
|
||||
// Papelito (Undercover) - Jeu de déduction
|
||||
gamesList.add(new GameInfo(
|
||||
"Undercover",
|
||||
"Papelito",
|
||||
"Trouvez l'undercover avant qu'il ne soit trop tard!",
|
||||
R.drawable.ic_undercover,
|
||||
R.drawable.ic_papelito,
|
||||
GameInfo.GameType.UNDERCOVER,
|
||||
false // Coming soon
|
||||
true // Available now
|
||||
));
|
||||
|
||||
// Jeux de règles
|
||||
@@ -111,7 +112,7 @@ public class GameSelectionActivity extends AppCompatActivity implements GameAdap
|
||||
startActivity(new Intent(this, com.example.boidelov3.games.game89.Game89SetupActivity.class));
|
||||
break;
|
||||
case UNDERCOVER:
|
||||
// TODO: Implémenter UndercoverSetupActivity
|
||||
startActivity(new Intent(this, com.example.boidelov3.games.papelito.PapelitoSetupActivity.class));
|
||||
break;
|
||||
case RULES:
|
||||
// TODO: Implémenter RulesListActivity
|
||||
|
||||
@@ -84,6 +84,9 @@ public class SecureConfig {
|
||||
* @return La clé API ou null si non trouvée
|
||||
*/
|
||||
public String getApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
return sharedPreferences.getString(key, null);
|
||||
}
|
||||
@@ -95,6 +98,11 @@ public class SecureConfig {
|
||||
* @return true si supprimée avec succès
|
||||
*/
|
||||
public boolean removeApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
Log.w(TAG, "Provider null ou vide pour removeApiKey");
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
editor.remove(key);
|
||||
@@ -127,6 +135,9 @@ public class SecureConfig {
|
||||
* @return true si une clé existe
|
||||
*/
|
||||
public boolean hasApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
return sharedPreferences.contains(key) && sharedPreferences.getString(key, null) != null;
|
||||
}
|
||||
@@ -143,9 +154,14 @@ public class SecureConfig {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
Log.w(TAG, "Provider null ou vide");
|
||||
return false;
|
||||
}
|
||||
|
||||
String trimmedKey = apiKey.trim();
|
||||
|
||||
switch (provider.toLowerCase()) {
|
||||
switch (provider.toLowerCase().trim()) {
|
||||
case "openai":
|
||||
// Les clés OpenAI commencent par "sk-"
|
||||
return trimmedKey.startsWith("sk-") && trimmedKey.length() >= 20;
|
||||
@@ -198,7 +214,10 @@ public class SecureConfig {
|
||||
* Retourne la clé SharedPreferences appropriée selon le provider
|
||||
*/
|
||||
private String getPrefKeyForProvider(String provider) {
|
||||
switch (provider.toLowerCase()) {
|
||||
if (provider == null) {
|
||||
return KEY_API_KEY; // Default to OpenAI key
|
||||
}
|
||||
switch (provider.toLowerCase().trim()) {
|
||||
case "openrouter":
|
||||
return KEY_API_KEY_OPENROUTER;
|
||||
case "zai":
|
||||
|
||||
Reference in New Issue
Block a user