forked from menault/TD3_DEV51_Qualite_Algo
Compare commits
1 Commits
JANNAIRE
...
cc14043782
| Author | SHA1 | Date | |
|---|---|---|---|
| cc14043782 |
@@ -2,129 +2,95 @@ package back;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Gère la bibliothèque de mots (chargement + sélection aléatoire).
|
||||
* - Lit "bibliothèque/mots.txt" OU "Bibliotheque/mots.txt" (UTF-8), 1 mot par ligne.
|
||||
* - Ignore lignes vides et commentaires (#...).
|
||||
* - Fournit des helpers pour tirer des mots selon la difficulté.
|
||||
* Fournit les mots pour le jeu.
|
||||
* - Lit d'abord "bibliothèque/mots.txt" (UTF-8), 1 mot par ligne.
|
||||
* - Ignore les lignes vides et celles qui commencent par '#'.
|
||||
* - Si le fichier est introuvable ou vide, bascule sur une liste par défaut.
|
||||
*/
|
||||
public class Words {
|
||||
|
||||
/** Chemins possibles (accents/casse) pour maximiser la compatibilité. */
|
||||
private static final Path[] CANDIDATES = new Path[] {
|
||||
Paths.get("bibliothèque", "mots.txt"),
|
||||
Paths.get("Bibliotheque", "mots.txt")
|
||||
};
|
||||
/** Chemin du fichier de mots (relatif à la racine du projet). */
|
||||
private static final Path WORDS_PATH = Paths.get("Bibliotheque", "mots.txt");
|
||||
|
||||
/** Liste de secours si aucun fichier trouvé/valide. */
|
||||
private static final List<String> DEFAULT = new ArrayList<>();
|
||||
static {
|
||||
Collections.addAll(DEFAULT,
|
||||
"algorithm","variable","function","interface","inheritance",
|
||||
"exception","compiler","database","network","architecture",
|
||||
"iteration","recursion","encryption","framework","protocol",
|
||||
"java","pendu","ordinateur","developpement","interface"
|
||||
/** Liste de secours si le fichier n'est pas disponible. */
|
||||
private static final List<String> DEFAULT = List.of(
|
||||
"algorithm", "variable", "function", "interface", "inheritance",
|
||||
"exception", "compiler", "database", "network", "architecture",
|
||||
"iteration", "recursion", "encryption", "framework", "protocol"
|
||||
);
|
||||
}
|
||||
|
||||
/** Cache mémoire + RNG. */
|
||||
private static volatile List<String> CACHE = null;
|
||||
/** RNG partagé et cache des mots chargés. */
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
private static volatile List<String> CACHE = null;
|
||||
|
||||
/** Renvoie la liste complète (copie) — charge une seule fois si nécessaire. */
|
||||
public static List<String> all() {
|
||||
ensureLoaded();
|
||||
return new ArrayList<>(CACHE);
|
||||
}
|
||||
|
||||
/** Renvoie un mot aléatoire dans tout le dictionnaire. */
|
||||
/**
|
||||
* Retourne un mot choisi au hasard depuis le fichier ou la liste par défaut.
|
||||
* Déclenche un chargement paresseux (lazy-load) si nécessaire.
|
||||
*/
|
||||
public static String random() {
|
||||
ensureLoaded();
|
||||
return CACHE.get(RNG.nextInt(CACHE.size()));
|
||||
}
|
||||
|
||||
/** Renvoie un mot aléatoire de moins de 8 lettres (sinon bascule sur random()). */
|
||||
public static String randomShortWord() {
|
||||
ensureLoaded();
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String w : CACHE) if (w.length() < 8) list.add(w);
|
||||
if (list.isEmpty()) return random();
|
||||
return list.get(RNG.nextInt(list.size()));
|
||||
}
|
||||
|
||||
/** Renvoie un mot aléatoire de 8 lettres ou plus (sinon bascule sur random()). */
|
||||
public static String randomLongWord() {
|
||||
ensureLoaded();
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String w : CACHE) if (w.length() >= 8) list.add(w);
|
||||
if (list.isEmpty()) return random();
|
||||
return list.get(RNG.nextInt(list.size()));
|
||||
}
|
||||
|
||||
/** Renvoie une paire [court, long] pour le niveau difficile. */
|
||||
public static List<String> randomPair() {
|
||||
List<String> pair = new ArrayList<>(2);
|
||||
pair.add(randomShortWord());
|
||||
pair.add(randomLongWord());
|
||||
return pair;
|
||||
}
|
||||
|
||||
/** Force le rechargement du fichier (au cas où le contenu change en cours d’exécution). */
|
||||
/**
|
||||
* Recharge les mots depuis le fichier. Utile si modification de mots.txt à chaud.
|
||||
*/
|
||||
public static synchronized void reload() {
|
||||
CACHE = loadFromFileOrDefault();
|
||||
}
|
||||
|
||||
// -------------------- internes --------------------
|
||||
|
||||
/** Charge le cache si nécessaire (thread-safe). */
|
||||
/** Garantit que le cache est initialisé. */
|
||||
private static void ensureLoaded() {
|
||||
if (CACHE == null) {
|
||||
synchronized (Words.class) {
|
||||
if (CACHE == null) CACHE = loadFromFileOrDefault();
|
||||
if (CACHE == null) {
|
||||
CACHE = loadFromFileOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Tente chaque chemin candidat, sinon retourne DEFAULT mélangé. */
|
||||
/** Tente de charger depuis le fichier, sinon renvoie la liste par défaut. */
|
||||
private static List<String> loadFromFileOrDefault() {
|
||||
List<String> data = readFirstExistingCandidate();
|
||||
if (data.isEmpty()) {
|
||||
data = new ArrayList<>(DEFAULT);
|
||||
}
|
||||
Collections.shuffle(data, RNG); // casse tout déterminisme initial
|
||||
return data;
|
||||
List<String> fromFile = readUtf8Lines(WORDS_PATH);
|
||||
if (fromFile.isEmpty()) return DEFAULT;
|
||||
return fromFile;
|
||||
}
|
||||
|
||||
/** Lit le premier fichier existant parmi les candidats. */
|
||||
private static List<String> readFirstExistingCandidate() {
|
||||
for (Path p : CANDIDATES) {
|
||||
List<String> list = readUtf8Trimmed(p);
|
||||
if (!list.isEmpty()) return list;
|
||||
/**
|
||||
* Lit toutes les lignes UTF-8 depuis le chemin fourni,
|
||||
* en filtrant vides et commentaires (# ...).
|
||||
*/
|
||||
private static List<String> readUtf8Lines(Path path) {
|
||||
List<String> result = new ArrayList<>();
|
||||
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
|
||||
lines.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.filter(s -> !s.startsWith("#"))
|
||||
.forEach(result::add);
|
||||
} catch (IOException e) {
|
||||
// Silencieux : on basculera sur DEFAULT
|
||||
}
|
||||
return new ArrayList<>();
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Lit un fichier UTF-8, filtre vides/commentaires, force lowercase. */
|
||||
private static List<String> readUtf8Trimmed(Path path) {
|
||||
List<String> out = new ArrayList<>();
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
|
||||
for (String raw : lines) {
|
||||
if (raw == null) continue;
|
||||
String s = raw.trim().toLowerCase();
|
||||
if (s.isEmpty() || s.startsWith("#")) continue;
|
||||
// On accepte lettres/accentuées + tiret (simple et robuste)
|
||||
if (s.matches("[a-zàâçéèêëîïôûùüÿñæœ-]+")) out.add(s);
|
||||
/* Retourne la liste complète des mots disponibles */
|
||||
public static List<String> all() {
|
||||
ensureLoaded();
|
||||
return new ArrayList<>(CACHE);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// on tombera sur DEFAULT
|
||||
}
|
||||
return out;
|
||||
|
||||
/* Petit utilitaire pour afficher un mot masqué dans les messages */
|
||||
public static String hiddenWord(Game g) {
|
||||
return g.maskedWord().replace(' ', '_');
|
||||
}
|
||||
}
|
||||
@@ -9,67 +9,64 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface graphique du pendu avec niveaux :
|
||||
* - 1 : mots < 8 lettres
|
||||
* - 2 : mots ≥ 8 lettres
|
||||
* - 3 : deux mots (score + chrono cumulés)
|
||||
* Boutons : Essayer / Nouvelle partie / Menu / Quitter.
|
||||
* (Toutes les méthodes ≤ 50 lignes)
|
||||
* Interface graphique du jeu du pendu avec gestion des difficultés.
|
||||
* - Niveau 1 : mots de moins de 8 lettres
|
||||
* - Niveau 2 : mots de 8 lettres ou plus
|
||||
* - Niveau 3 : deux mots à deviner à la suite (un court + un long)
|
||||
* Toutes les méthodes ≤ 50 lignes.
|
||||
*/
|
||||
public class GameUI {
|
||||
private JFrame frame;
|
||||
private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel;
|
||||
private JTextField input;
|
||||
private JButton tryBtn, newBtn, menuBtn, quitBtn;
|
||||
private JButton tryBtn, quitBtn, menuBtn;
|
||||
|
||||
private Game game;
|
||||
private List<String> words;
|
||||
private int index = 0;
|
||||
private final int level;
|
||||
private int level;
|
||||
private String currentWord = "";
|
||||
private Timer timer;
|
||||
|
||||
// Cumul de session (niveau 3)
|
||||
private long sessionStartNano = -1L;
|
||||
private int sessionScore = 0;
|
||||
|
||||
/** Reçoit la difficulté (1, 2, 3). */
|
||||
/** Constructeur : reçoit la difficulté (1, 2 ou 3) */
|
||||
public GameUI(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/** Affiche la fenêtre et lance la session. */
|
||||
/** Affiche la fenêtre et lance la partie (ou séquence) */
|
||||
public void show() {
|
||||
setupWindow();
|
||||
setupLayout();
|
||||
setupActions();
|
||||
startNewSession();
|
||||
prepareWords();
|
||||
startRound();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
/** Démarre une nouvelle session (nouveaux mots, reset chrono/score session). */
|
||||
private void startNewSession() {
|
||||
sessionStartNano = System.nanoTime();
|
||||
sessionScore = 0;
|
||||
index = 0;
|
||||
prepareWords();
|
||||
startRound();
|
||||
}
|
||||
|
||||
/** Prépare la liste des mots selon le niveau (aléatoire géré dans Words). */
|
||||
/** Prépare la liste des mots selon la difficulté choisie */
|
||||
private void prepareWords() {
|
||||
words = new ArrayList<>();
|
||||
if (level == 1) {
|
||||
words.add(Words.randomShortWord());
|
||||
} else if (level == 2) {
|
||||
words.add(Words.randomLongWord());
|
||||
} else if (level == 3) {
|
||||
words = Words.randomPair(); // [court, long] aléatoires
|
||||
words = new ArrayList<String>();
|
||||
List<String> all = Words.all();
|
||||
|
||||
if (level == 1) { // mots courts
|
||||
for (String w : all) if (w.length() < 8) words.add(w);
|
||||
} else if (level == 2) { // mots longs
|
||||
for (String w : all) if (w.length() >= 8) words.add(w);
|
||||
} else if (level == 3) { // un court + un long
|
||||
String shortW = null, longW = null;
|
||||
for (String w : all) {
|
||||
if (shortW == null && w.length() < 8) shortW = w;
|
||||
if (longW == null && w.length() >= 8) longW = w;
|
||||
if (shortW != null && longW != null) break;
|
||||
}
|
||||
if (words.isEmpty()) words.add(Words.random()); // filet de sécurité
|
||||
if (shortW != null) words.add(shortW);
|
||||
if (longW != null) words.add(longW);
|
||||
}
|
||||
|
||||
/** Fenêtre principale. */
|
||||
if (words.isEmpty()) words.add(Words.random());
|
||||
}
|
||||
|
||||
/** Crée la fenêtre principale */
|
||||
private void setupWindow() {
|
||||
frame = new JFrame("Jeu du Pendu");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
@@ -78,7 +75,7 @@ public class GameUI {
|
||||
frame.setLayout(new BorderLayout(12, 12));
|
||||
}
|
||||
|
||||
/** Layout + composants. */
|
||||
/** Construit la mise en page et les composants */
|
||||
private void setupLayout() {
|
||||
imgLabel = new JLabel("", SwingConstants.CENTER);
|
||||
frame.add(imgLabel, BorderLayout.CENTER);
|
||||
@@ -93,15 +90,14 @@ public class GameUI {
|
||||
top.add(buildTopLine(triedLabel, timeLabel));
|
||||
frame.add(top, BorderLayout.NORTH);
|
||||
|
||||
// --- Bas : boutons + saisie
|
||||
JPanel bottom = new JPanel(new BorderLayout(8, 8));
|
||||
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
input = new JTextField(5);
|
||||
tryBtn = new JButton("Essayer");
|
||||
newBtn = new JButton("Nouvelle partie");
|
||||
inputPanel.add(new JLabel("Lettre :"));
|
||||
inputPanel.add(input);
|
||||
inputPanel.add(tryBtn);
|
||||
inputPanel.add(newBtn);
|
||||
|
||||
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
menuBtn = new JButton("Menu");
|
||||
@@ -114,7 +110,7 @@ public class GameUI {
|
||||
frame.add(bottom, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
/** Ligne d’infos (gauche/droite). */
|
||||
/** Construit une ligne (label gauche + espace + label droit) */
|
||||
private JPanel buildTopLine(JLabel left, JLabel right) {
|
||||
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
p.add(left);
|
||||
@@ -123,17 +119,16 @@ public class GameUI {
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Actions + timer. */
|
||||
/** Branche les actions + timer */
|
||||
private void setupActions() {
|
||||
tryBtn.addActionListener(this::onTry);
|
||||
input.addActionListener(this::onTry);
|
||||
newBtn.addActionListener(e -> startNewSession());
|
||||
menuBtn.addActionListener(e -> returnToMenu());
|
||||
quitBtn.addActionListener(e -> frame.dispose());
|
||||
menuBtn.addActionListener(e -> returnToMenu());
|
||||
timer = new Timer(1000, e -> refreshStatsOnly());
|
||||
}
|
||||
|
||||
/** Retour vers le menu de sélection. */
|
||||
/** Retour au menu principal */
|
||||
private void returnToMenu() {
|
||||
timer.stop();
|
||||
frame.dispose();
|
||||
@@ -141,12 +136,11 @@ public class GameUI {
|
||||
menu.show();
|
||||
}
|
||||
|
||||
/** Démarre un nouveau mot (ou termine la session si plus de mots). */
|
||||
/** Démarre un nouveau mot (ou termine au niveau 3) */
|
||||
private void startRound() {
|
||||
if (index >= words.size()) {
|
||||
int total = (level == 3) ? sessionScore : game.getScore();
|
||||
long secs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
|
||||
showMsg("Niveau terminé !\nScore total : " + total + "\nTemps : " + secs + "s");
|
||||
showMsg("🎉 Niveau terminé !");
|
||||
frame.dispose();
|
||||
return;
|
||||
}
|
||||
currentWord = words.get(index++);
|
||||
@@ -157,7 +151,7 @@ public class GameUI {
|
||||
refreshUI();
|
||||
}
|
||||
|
||||
/** Tente une lettre (bouton/Entrée). */
|
||||
/** Tente une lettre (clic bouton ou Entrée) */
|
||||
private void onTry(ActionEvent e) {
|
||||
String text = input.getText();
|
||||
if (!Check.isLetter(text)) {
|
||||
@@ -173,28 +167,24 @@ public class GameUI {
|
||||
checkEnd();
|
||||
}
|
||||
|
||||
/** Fin du mot → affiche popup et enchaîne (lvl 3 cumule score/chrono). */
|
||||
/** Vérifie la fin du mot et enchaîne si besoin (niveau 3) */
|
||||
private void checkEnd() {
|
||||
if (!(game.isWin() || game.isLose())) return;
|
||||
|
||||
timer.stop();
|
||||
game.end(game.isWin());
|
||||
int displayScore = (level == 3) ? sessionScore + game.getScore() : game.getScore();
|
||||
long displaySecs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
|
||||
|
||||
showMsg((game.isWin() ? "Bravo !" : "Perdu !")
|
||||
+ " Le mot était : " + currentWord
|
||||
+ "\nScore : " + displayScore
|
||||
+ "\nTemps : " + displaySecs + "s");
|
||||
String verdict = game.isWin() ? "Bravo !" : "Perdu !";
|
||||
String msg = verdict + " Le mot était : " + currentWord
|
||||
+ "\nScore : " + game.getScore()
|
||||
+ "\nTemps : " + game.getElapsedSeconds() + "s";
|
||||
showMsg(msg);
|
||||
|
||||
if (level == 3 && index < words.size()) {
|
||||
sessionScore += game.getScore();
|
||||
startRound();
|
||||
} else {
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/** Refresh complet. */
|
||||
/** Rafraîchit image + textes + stats */
|
||||
private void refreshUI() {
|
||||
imgLabel.setIcon(Gallows.icon(game.getErrors()));
|
||||
wordLabel.setText("Mot : " + game.maskedWord());
|
||||
@@ -203,23 +193,13 @@ public class GameUI {
|
||||
frame.repaint();
|
||||
}
|
||||
|
||||
/** Refresh stats (score/chrono). */
|
||||
/** Rafraîchit uniquement score + chrono */
|
||||
private void refreshStatsOnly() {
|
||||
int s = (level == 3) ? sessionScore + game.getScore() : game.getScore();
|
||||
long t = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
|
||||
scoreLabel.setText("Score : " + s);
|
||||
timeLabel.setText("Temps : " + t + "s");
|
||||
scoreLabel.setText("Score : " + game.getScore());
|
||||
timeLabel.setText("Temps : " + game.getElapsedSeconds() + "s");
|
||||
}
|
||||
|
||||
/** Secondes écoulées depuis le début de la session (niveau 3). */
|
||||
private long getSessionSeconds() {
|
||||
long now = System.nanoTime();
|
||||
long delta = now - sessionStartNano;
|
||||
if (delta < 0) delta = 0;
|
||||
return delta / 1_000_000_000L;
|
||||
}
|
||||
|
||||
/** Popup info. */
|
||||
/** Affiche un message */
|
||||
private void showMsg(String msg) {
|
||||
JOptionPane.showMessageDialog(frame, msg);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12
README.md
12
README.md
@@ -5,11 +5,13 @@
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
Affichage d’une **image différente du pendu** à chaque erreur
|
||||
Lecture de mots depuis un **fichier externe** (`bibliothèque/mots.txt`)
|
||||
Validation des entrées (une seule LETTRE à la fois)
|
||||
Bouton **“Nouvelle partie”** pour rejouer sans relancer le programme
|
||||
Messages de victoire / défaite
|
||||
- Affichage d’une **image différente du pendu** à chaque erreur
|
||||
- Lecture de mots depuis un **fichier externe** (`bibliothèque/mots.txt`)
|
||||
- Validation des entrées (une seule LETTRE à la fois)
|
||||
- Bouton **“Nouvelle partie”** pour rejouer sans relancer le programme
|
||||
- Messages de victoire / défaite
|
||||
- Score + Chronomètre en direct
|
||||
- Sélection du niveau dans le menu avant de jouer
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user