fix(classic): anti-répétition persistante + cohérence des questions
Anti-répétition: - Chaque question posée est marquée en SharedPreferences (par ID): elle ne revient plus, même entre les parties, tant que le stock de 185 questions n'est pas épuisé (reset automatique ensuite) - Le fallback ne renvoie plus une question déjà posée Cohérence (audit croisé JSON <-> moteur, 185 questions): - Accord du verbe selon le sujet réel: 62 questions à sujet pluriel recevaient «boit» au lieu de «boivent» (isPluralSubject) - Remplacement indépendant des balises <J1>/<J2>/<J3>: une question contenant <J2> sans <J1> n'affichait plus la balise brute - Le champ arret du JSON est désormais utilisé comme message de fin de défi (27 messages étaient des données mortes) - id 84: champ "distribut" (typo) jamais lu -> question muette - 16 questions à double consigne (gorgées en dur + gorgées du moteur) passées en auto-suffisantes - Balises <variante> manquantes (ids 21, 97) rétablies - 78 corrections de langue/accord dans les textes (orthographe, accords m/f, variantes vieux/vieille, branché(e), fins de phrases incohérentes avec le verbe ajouté...)
This commit is contained in:
@@ -98,6 +98,13 @@ public class QuestionRepository {
|
||||
return Result.success(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'ensemble des IDs des questions déjà posées (toutes parties confondues).
|
||||
*/
|
||||
public Set<String> getAskedQuestionIds() {
|
||||
return new HashSet<>(prefs.getStringSet(KEY_ASKED_QUESTIONS, new HashSet<>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marque une question comme étant posée.
|
||||
*/
|
||||
|
||||
@@ -100,23 +100,25 @@ public class GameEngine {
|
||||
String result = questionText;
|
||||
int playerCount = 0;
|
||||
|
||||
if (hasJ1 && hasJ2 && hasJ3 && selectedPlayers.size() >= 3) {
|
||||
playerCount = 3;
|
||||
// Chaque balise est remplacée indépendamment : une question contenant
|
||||
// uniquement <J2> ou <J3> reste jouable (l'ordre J1, J2, J3 est conservé)
|
||||
if (hasJ1 && selectedPlayers.size() >= 1) {
|
||||
playerCount++;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
}
|
||||
if (hasJ2 && selectedPlayers.size() >= 2) {
|
||||
playerCount++;
|
||||
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||
}
|
||||
if (hasJ3 && selectedPlayers.size() >= 3) {
|
||||
playerCount++;
|
||||
result = result.replace("<J3>", selectedPlayers.get(2));
|
||||
} else if (hasJ1 && hasJ2 && selectedPlayers.size() >= 2) {
|
||||
playerCount = 2;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||
} else if (hasJ1 && selectedPlayers.size() >= 1) {
|
||||
playerCount = 1;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
}
|
||||
|
||||
return new PlayerReplaceResult(result, playerCount);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute le texte des gorgées à la question.
|
||||
*/
|
||||
@@ -130,9 +132,11 @@ public class GameEngine {
|
||||
|
||||
int totalGorgees = question.getGorger() + addedGorgees;
|
||||
|
||||
// Accord du verbe selon le nombre de joueurs
|
||||
String boisVerb = (playerCount > 1) ? "boivent" : "boit";
|
||||
String distribueVerb = (playerCount > 1) ? "distribuent" : "distribue";
|
||||
// Accord du verbe selon le véritable sujet de la phrase
|
||||
boolean pluriel = isPluralSubject(questionText);
|
||||
String boisVerb = pluriel ? "boivent" : "boit";
|
||||
String distribueVerb = pluriel ? "distribuent" : "distribue";
|
||||
|
||||
|
||||
// Déterminer si boire ou distribuer
|
||||
if (question.isRecois() && question.isDistribution()) {
|
||||
@@ -152,6 +156,31 @@ public class GameEngine {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique si le sujet de la question est pluriel, pour accorder le verbe
|
||||
* (boit/boivent, distribue/distribuent) ajouté en fin de question.
|
||||
*/
|
||||
private static boolean isPluralSubject(String questionText) {
|
||||
String t = questionText.trim().toLowerCase();
|
||||
if (t.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// Sujet de groupe en tête de question : « Ceux qui… », « Les fans de… », « Tous ceux qui… »
|
||||
if (t.startsWith("ceux") || t.startsWith("celles") || t.startsWith("tous ") || t.startsWith("toutes ")
|
||||
|| t.startsWith("les ")) {
|
||||
return true;
|
||||
}
|
||||
// Sujet pluriel en toute fin de phrase, juste avant le verbe ajouté
|
||||
String[] finsPlurielles = {"perdants", "joueurs", "joueuses", "gens", "ceux", "celles", "eux", "elles",
|
||||
"abonnés", "abonnées", "végans", "célibataires", "filles", "garçons", "couples"};
|
||||
for (String fin : finsPlurielles) {
|
||||
if (t.endsWith(fin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne n joueurs aléatoires uniques.
|
||||
*/
|
||||
|
||||
+79
-20
@@ -191,6 +191,24 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
if (result.isSuccess()) {
|
||||
Questions q = result.getData();
|
||||
questions = q != null && q.getQuestions() != null ? q.getQuestions() : new ArrayList<>();
|
||||
|
||||
// Exclure les questions déjà posées lors des parties précédentes,
|
||||
// pour qu'une question ne revienne pas tant que tout le stock n'a pas été épuisé
|
||||
java.util.Set<String> askedIds = questionRepository.getAskedQuestionIds();
|
||||
if (!askedIds.isEmpty()) {
|
||||
List<Question> unasked = new ArrayList<>();
|
||||
for (Question question : questions) {
|
||||
if (!askedIds.contains(String.valueOf(question.getId()))) {
|
||||
unasked.add(question);
|
||||
}
|
||||
}
|
||||
if (unasked.isEmpty()) {
|
||||
// Toutes les questions ont été posées : on repart sur le stock complet
|
||||
questionRepository.resetAskedQuestions();
|
||||
} else {
|
||||
questions = unasked;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
android.util.Log.e("BoideloClassicGame", "Erreur lors du chargement des questions: " + result.getError());
|
||||
questions = new ArrayList<>();
|
||||
@@ -407,10 +425,19 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
askedQuestionIndices.add(questionIndex);
|
||||
// Persistance : la question ne reviendra plus lors des prochaines parties
|
||||
questionRepository.markQuestionAsAsked(question.getId());
|
||||
return question;
|
||||
}
|
||||
|
||||
// Si on ne trouve pas de question valide, retourner une question aléatoire
|
||||
// Si on ne trouve pas de question valide (toutes les restantes ont <manches>),
|
||||
// retourner une question non posée plutôt qu'une question au hasard
|
||||
if (!availableIndices.isEmpty()) {
|
||||
int fallbackIndex = availableIndices.get(random.nextInt(availableIndices.size()));
|
||||
askedQuestionIndices.add(fallbackIndex);
|
||||
questionRepository.markQuestionAsAsked(questions.get(fallbackIndex).getId());
|
||||
return questions.get(fallbackIndex);
|
||||
}
|
||||
return questions.get(random.nextInt(questions.size()));
|
||||
}
|
||||
|
||||
@@ -825,8 +852,10 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
question.setManchesRestantes(nbaleatoiremanches);
|
||||
|
||||
// Définir le message de fin de défi
|
||||
String stopMessage = "Fin de défi!\n" + questionText;
|
||||
question.setArretMessageManche(stopMessage);
|
||||
// Message de fin de défi : texte d'arrêt dédié s'il existe, sinon rappel de la consigne
|
||||
String arret = question.getArret();
|
||||
String stopMessage = "Fin de défi !\n" + (arret != null && !arret.trim().isEmpty() ? arret : questionText);
|
||||
|
||||
|
||||
questionsAvecManches.add(question);
|
||||
|
||||
@@ -873,24 +902,25 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
if (isJoueurs1 || isJoueurs2 || isJoueurs3) {
|
||||
List<String> aleatoirejoueurs = TroisJoueurAleatoire();
|
||||
|
||||
if (isJoueurs1 && isJoueurs2 && isJoueurs3 && aleatoirejoueurs.size() >= RANDOM_PLAYER_SELECTION_COUNT) {
|
||||
playerCount = 3;
|
||||
j1Name = aleatoirejoueurs.get(0);
|
||||
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
|
||||
questionText = questionText.replace("<J2>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(1)));
|
||||
questionText = questionText.replace("<J3>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(2)));
|
||||
} else if (isJoueurs1 && isJoueurs2 && aleatoirejoueurs.size() >= TWO_PLAYERS) {
|
||||
playerCount = 2;
|
||||
j1Name = aleatoirejoueurs.get(0);
|
||||
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
|
||||
questionText = questionText.replace("<J2>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(1)));
|
||||
} else if (isJoueurs1 && aleatoirejoueurs.size() >= ONE_PLAYER) {
|
||||
playerCount = 1;
|
||||
// Chaque balise est remplacée indépendamment : une question contenant
|
||||
// uniquement <J2> ou <J3> reste jouable (l'ordre J1, J2, J3 est conservé)
|
||||
if (isJoueurs1 && aleatoirejoueurs.size() >= ONE_PLAYER) {
|
||||
playerCount++;
|
||||
j1Name = aleatoirejoueurs.get(0);
|
||||
questionText = questionText.replace("<J1>", ErrorHandler.escapeHtml(j1Name));
|
||||
}
|
||||
if (isJoueurs2 && aleatoirejoueurs.size() >= TWO_PLAYERS) {
|
||||
playerCount++;
|
||||
questionText = questionText.replace("<J2>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(1)));
|
||||
}
|
||||
if (isJoueurs3 && aleatoirejoueurs.size() >= RANDOM_PLAYER_SELECTION_COUNT) {
|
||||
playerCount++;
|
||||
questionText = questionText.replace("<J3>", ErrorHandler.escapeHtml(aleatoirejoueurs.get(2)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new PlayerSelectionResult(questionText, j1Name, playerCount);
|
||||
}
|
||||
|
||||
@@ -949,15 +979,19 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine l'action (boit/boivent ou distribue/distribuent) selon le nombre de joueurs
|
||||
/**
|
||||
* Détermine l'action (boit/boivent ou distribue/distribuent) selon le véritable
|
||||
* sujet de la phrase, pour un accord grammatical correct.
|
||||
*/
|
||||
private ActionChoiceResult determineActionChoice(Question question, String questionText, int playerCount) {
|
||||
boolean isBois = false;
|
||||
boolean isDistribue = false;
|
||||
|
||||
// Accord du verbe selon le nombre de joueurs
|
||||
String boisVerb = (playerCount > 1) ? "boivent" : "boit";
|
||||
String distribueVerb = (playerCount > 1) ? "distribuent" : "distribue";
|
||||
// Accord du verbe selon le sujet réel de la question
|
||||
boolean pluriel = isPluralSubject(questionText);
|
||||
String boisVerb = pluriel ? "boivent" : "boit";
|
||||
String distribueVerb = pluriel ? "distribuent" : "distribue";
|
||||
|
||||
|
||||
if (question.isRecois() && question.isDistribution()) {
|
||||
boolean rand = random.nextBoolean();
|
||||
@@ -979,6 +1013,31 @@ public class BoideloClassicGameActivity extends AppCompatActivity {
|
||||
return new ActionChoiceResult(questionText, isBois, isDistribue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique si le sujet de la question est pluriel, pour accorder le verbe
|
||||
* (boit/boivent, distribue/distribuent) ajouté en fin de question.
|
||||
*/
|
||||
private boolean isPluralSubject(String questionText) {
|
||||
String t = questionText.trim().toLowerCase();
|
||||
if (t.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// Sujet de groupe en tête de question : « Ceux qui… », « Les fans de… », « Tous ceux qui… »
|
||||
if (t.startsWith("ceux") || t.startsWith("celles") || t.startsWith("tous ") || t.startsWith("toutes ")
|
||||
|| t.startsWith("les ")) {
|
||||
return true;
|
||||
}
|
||||
// Sujet pluriel en toute fin de phrase, juste avant le verbe ajouté
|
||||
String[] finsPlurielles = {"perdants", "joueurs", "joueuses", "gens", "ceux", "celles", "eux", "elles",
|
||||
"abonnés", "abonnées", "végans", "célibataires", "filles", "garçons", "couples"};
|
||||
for (String fin : finsPlurielles) {
|
||||
if (t.endsWith(fin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute le nombre de gorgées au texte de la question
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user