diff --git a/Jeu_pendu/Back/Words.java b/Jeu_pendu/Back/Words.java index 7632427..8ae5e15 100644 --- a/Jeu_pendu/Back/Words.java +++ b/Jeu_pendu/Back/Words.java @@ -2,95 +2,129 @@ package back; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.stream.Stream; /** - * 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. + * 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é. */ public class Words { - /** Chemin du fichier de mots (relatif à la racine du projet). */ - private static final Path WORDS_PATH = Paths.get("Bibliotheque", "mots.txt"); + /** 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") + }; - /** Liste de secours si le fichier n'est pas disponible. */ - private static final List DEFAULT = List.of( - "algorithm", "variable", "function", "interface", "inheritance", - "exception", "compiler", "database", "network", "architecture", - "iteration", "recursion", "encryption", "framework", "protocol" - ); + /** Liste de secours si aucun fichier trouvé/valide. */ + private static final List 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" + ); + } - /** RNG partagé et cache des mots chargés. */ - private static final SecureRandom RNG = new SecureRandom(); + /** Cache mémoire + RNG. */ private static volatile List CACHE = null; + private static final SecureRandom RNG = new SecureRandom(); - /** - * 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())); - } - - /** - * Recharge les mots depuis le fichier. Utile si modification de mots.txt à chaud. - */ - public static synchronized void reload() { - CACHE = loadFromFileOrDefault(); - } - - /** Garantit que le cache est initialisé. */ - private static void ensureLoaded() { - if (CACHE == null) { - synchronized (Words.class) { - if (CACHE == null) { - CACHE = loadFromFileOrDefault(); - } - } - } - } - - /** Tente de charger depuis le fichier, sinon renvoie la liste par défaut. */ - private static List loadFromFileOrDefault() { - List fromFile = readUtf8Lines(WORDS_PATH); - if (fromFile.isEmpty()) return DEFAULT; - return fromFile; - } - - /** - * Lit toutes les lignes UTF-8 depuis le chemin fourni, - * en filtrant vides et commentaires (# ...). - */ - private static List readUtf8Lines(Path path) { - List result = new ArrayList<>(); - try (Stream 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 result; - } - - /* Retourne la liste complète des mots disponibles */ + /** Renvoie la liste complète (copie) — charge une seule fois si nécessaire. */ public static List all() { ensureLoaded(); return new ArrayList<>(CACHE); } - /* Petit utilitaire pour afficher un mot masqué dans les messages */ - public static String hiddenWord(Game g) { - return g.maskedWord().replace(' ', '_'); + /** Renvoie un mot aléatoire dans tout le dictionnaire. */ + public static String random() { + ensureLoaded(); + return CACHE.get(RNG.nextInt(CACHE.size())); } -} \ No newline at end of file + + /** Renvoie un mot aléatoire de moins de 8 lettres (sinon bascule sur random()). */ + public static String randomShortWord() { + ensureLoaded(); + List 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 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 randomPair() { + List 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). */ + public static synchronized void reload() { + CACHE = loadFromFileOrDefault(); + } + + // -------------------- internes -------------------- + + /** Charge le cache si nécessaire (thread-safe). */ + private static void ensureLoaded() { + if (CACHE == null) { + synchronized (Words.class) { + if (CACHE == null) CACHE = loadFromFileOrDefault(); + } + } + } + + /** Tente chaque chemin candidat, sinon retourne DEFAULT mélangé. */ + private static List loadFromFileOrDefault() { + List data = readFirstExistingCandidate(); + if (data.isEmpty()) { + data = new ArrayList<>(DEFAULT); + } + Collections.shuffle(data, RNG); // casse tout déterminisme initial + return data; + } + + /** Lit le premier fichier existant parmi les candidats. */ + private static List readFirstExistingCandidate() { + for (Path p : CANDIDATES) { + List list = readUtf8Trimmed(p); + if (!list.isEmpty()) return list; + } + return new ArrayList<>(); + } + + /** Lit un fichier UTF-8, filtre vides/commentaires, force lowercase. */ + private static List readUtf8Trimmed(Path path) { + List out = new ArrayList<>(); + try { + List 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); + } + } catch (IOException ignored) { + // on tombera sur DEFAULT + } + return out; + } +} diff --git a/Jeu_pendu/Front/GameUI.java b/Jeu_pendu/Front/GameUI.java index 211598f..5337347 100644 --- a/Jeu_pendu/Front/GameUI.java +++ b/Jeu_pendu/Front/GameUI.java @@ -9,64 +9,67 @@ import java.util.ArrayList; import java.util.List; /** - * 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. + * 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) */ public class GameUI { private JFrame frame; private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel; private JTextField input; - private JButton tryBtn, quitBtn, menuBtn; + private JButton tryBtn, newBtn, menuBtn, quitBtn; private Game game; private List words; private int index = 0; - private int level; + private final int level; private String currentWord = ""; private Timer timer; - /** Constructeur : reçoit la difficulté (1, 2 ou 3) */ + // Cumul de session (niveau 3) + private long sessionStartNano = -1L; + private int sessionScore = 0; + + /** Reçoit la difficulté (1, 2, 3). */ public GameUI(int level) { this.level = level; } - /** Affiche la fenêtre et lance la partie (ou séquence) */ + /** Affiche la fenêtre et lance la session. */ public void show() { setupWindow(); setupLayout(); setupActions(); - prepareWords(); - startRound(); + startNewSession(); frame.setVisible(true); } - /** Prépare la liste des mots selon la difficulté choisie */ - private void prepareWords() { - words = new ArrayList(); - List 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 (shortW != null) words.add(shortW); - if (longW != null) words.add(longW); - } - - if (words.isEmpty()) words.add(Words.random()); + /** Démarre une nouvelle session (nouveaux mots, reset chrono/score session). */ + private void startNewSession() { + sessionStartNano = System.nanoTime(); + sessionScore = 0; + index = 0; + prepareWords(); + startRound(); } - /** Crée la fenêtre principale */ + /** Prépare la liste des mots selon le niveau (aléatoire géré dans Words). */ + 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 + } + if (words.isEmpty()) words.add(Words.random()); // filet de sécurité + } + + /** Fenêtre principale. */ private void setupWindow() { frame = new JFrame("Jeu du Pendu"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -75,7 +78,7 @@ public class GameUI { frame.setLayout(new BorderLayout(12, 12)); } - /** Construit la mise en page et les composants */ + /** Layout + composants. */ private void setupLayout() { imgLabel = new JLabel("", SwingConstants.CENTER); frame.add(imgLabel, BorderLayout.CENTER); @@ -90,14 +93,15 @@ 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"); @@ -110,7 +114,7 @@ public class GameUI { frame.add(bottom, BorderLayout.SOUTH); } - /** Construit une ligne (label gauche + espace + label droit) */ + /** Ligne d’infos (gauche/droite). */ private JPanel buildTopLine(JLabel left, JLabel right) { JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); p.add(left); @@ -119,16 +123,17 @@ public class GameUI { return p; } - /** Branche les actions + timer */ + /** Actions + timer. */ private void setupActions() { tryBtn.addActionListener(this::onTry); input.addActionListener(this::onTry); - quitBtn.addActionListener(e -> frame.dispose()); + newBtn.addActionListener(e -> startNewSession()); menuBtn.addActionListener(e -> returnToMenu()); + quitBtn.addActionListener(e -> frame.dispose()); timer = new Timer(1000, e -> refreshStatsOnly()); } - /** Retour au menu principal */ + /** Retour vers le menu de sélection. */ private void returnToMenu() { timer.stop(); frame.dispose(); @@ -136,11 +141,12 @@ public class GameUI { menu.show(); } - /** Démarre un nouveau mot (ou termine au niveau 3) */ + /** Démarre un nouveau mot (ou termine la session si plus de mots). */ private void startRound() { if (index >= words.size()) { - showMsg("🎉 Niveau terminé !"); - frame.dispose(); + int total = (level == 3) ? sessionScore : game.getScore(); + long secs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds(); + showMsg("Niveau terminé !\nScore total : " + total + "\nTemps : " + secs + "s"); return; } currentWord = words.get(index++); @@ -151,7 +157,7 @@ public class GameUI { refreshUI(); } - /** Tente une lettre (clic bouton ou Entrée) */ + /** Tente une lettre (bouton/Entrée). */ private void onTry(ActionEvent e) { String text = input.getText(); if (!Check.isLetter(text)) { @@ -167,24 +173,28 @@ public class GameUI { checkEnd(); } - /** Vérifie la fin du mot et enchaîne si besoin (niveau 3) */ + /** Fin du mot → affiche popup et enchaîne (lvl 3 cumule score/chrono). */ private void checkEnd() { if (!(game.isWin() || game.isLose())) return; - timer.stop(); game.end(game.isWin()); - String verdict = game.isWin() ? "Bravo !" : "Perdu !"; - String msg = verdict + " Le mot était : " + currentWord - + "\nScore : " + game.getScore() - + "\nTemps : " + game.getElapsedSeconds() + "s"; - showMsg(msg); + 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"); if (level == 3 && index < words.size()) { + sessionScore += game.getScore(); startRound(); + } else { + timer.stop(); } } - /** Rafraîchit image + textes + stats */ + /** Refresh complet. */ private void refreshUI() { imgLabel.setIcon(Gallows.icon(game.getErrors())); wordLabel.setText("Mot : " + game.maskedWord()); @@ -193,13 +203,23 @@ public class GameUI { frame.repaint(); } - /** Rafraîchit uniquement score + chrono */ + /** Refresh stats (score/chrono). */ private void refreshStatsOnly() { - scoreLabel.setText("Score : " + game.getScore()); - timeLabel.setText("Temps : " + game.getElapsedSeconds() + "s"); + 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"); } - /** Affiche un message */ + /** 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. */ private void showMsg(String msg) { JOptionPane.showMessageDialog(frame, msg); } diff --git a/Jeu_pendu/out/back/Check.class b/Jeu_pendu/out/back/Check.class new file mode 100644 index 0000000..95b07da Binary files /dev/null and b/Jeu_pendu/out/back/Check.class differ diff --git a/Jeu_pendu/out/back/Game.class b/Jeu_pendu/out/back/Game.class new file mode 100644 index 0000000..73a4802 Binary files /dev/null and b/Jeu_pendu/out/back/Game.class differ diff --git a/Jeu_pendu/out/back/Result.class b/Jeu_pendu/out/back/Result.class new file mode 100644 index 0000000..33ddf15 Binary files /dev/null and b/Jeu_pendu/out/back/Result.class differ diff --git a/Jeu_pendu/out/back/Words.class b/Jeu_pendu/out/back/Words.class new file mode 100644 index 0000000..1762e71 Binary files /dev/null and b/Jeu_pendu/out/back/Words.class differ diff --git a/Jeu_pendu/out/front/Gallows$BufferedImage.class b/Jeu_pendu/out/front/Gallows$BufferedImage.class new file mode 100644 index 0000000..523522f Binary files /dev/null and b/Jeu_pendu/out/front/Gallows$BufferedImage.class differ diff --git a/Jeu_pendu/out/front/Gallows.class b/Jeu_pendu/out/front/Gallows.class new file mode 100644 index 0000000..a93d73d Binary files /dev/null and b/Jeu_pendu/out/front/Gallows.class differ diff --git a/Jeu_pendu/out/front/GameUI$1.class b/Jeu_pendu/out/front/GameUI$1.class new file mode 100644 index 0000000..9d394d7 Binary files /dev/null and b/Jeu_pendu/out/front/GameUI$1.class differ diff --git a/Jeu_pendu/out/front/GameUI.class b/Jeu_pendu/out/front/GameUI.class new file mode 100644 index 0000000..ccdcae1 Binary files /dev/null and b/Jeu_pendu/out/front/GameUI.class differ diff --git a/Jeu_pendu/out/front/MenuUI.class b/Jeu_pendu/out/front/MenuUI.class new file mode 100644 index 0000000..19bb729 Binary files /dev/null and b/Jeu_pendu/out/front/MenuUI.class differ diff --git a/Jeu_pendu/out/main/Main.class b/Jeu_pendu/out/main/Main.class new file mode 100644 index 0000000..fff9537 Binary files /dev/null and b/Jeu_pendu/out/main/Main.class differ diff --git a/out/back/Check.class b/out/back/Check.class new file mode 100644 index 0000000..95b07da Binary files /dev/null and b/out/back/Check.class differ diff --git a/out/back/Game.class b/out/back/Game.class new file mode 100644 index 0000000..73a4802 Binary files /dev/null and b/out/back/Game.class differ diff --git a/out/back/Result.class b/out/back/Result.class new file mode 100644 index 0000000..33ddf15 Binary files /dev/null and b/out/back/Result.class differ diff --git a/out/back/Words.class b/out/back/Words.class new file mode 100644 index 0000000..cf6b8bb Binary files /dev/null and b/out/back/Words.class differ diff --git a/out/front/Gallows$BufferedImage.class b/out/front/Gallows$BufferedImage.class new file mode 100644 index 0000000..523522f Binary files /dev/null and b/out/front/Gallows$BufferedImage.class differ diff --git a/out/front/Gallows.class b/out/front/Gallows.class new file mode 100644 index 0000000..a93d73d Binary files /dev/null and b/out/front/Gallows.class differ diff --git a/out/front/GameUI$1.class b/out/front/GameUI$1.class new file mode 100644 index 0000000..97c1d2c Binary files /dev/null and b/out/front/GameUI$1.class differ diff --git a/out/front/GameUI.class b/out/front/GameUI.class new file mode 100644 index 0000000..995da7f Binary files /dev/null and b/out/front/GameUI.class differ diff --git a/out/main/Main.class b/out/main/Main.class new file mode 100644 index 0000000..e1df1b8 Binary files /dev/null and b/out/main/Main.class differ