package back; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 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 { /** 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 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" ); } /** Cache mémoire + RNG. */ private static volatile List CACHE = null; private static final SecureRandom RNG = new SecureRandom(); /** Renvoie la liste complète (copie) — charge une seule fois si nécessaire. */ public static List all() { ensureLoaded(); return new ArrayList<>(CACHE); } /** Renvoie un mot aléatoire dans tout le dictionnaire. */ 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 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; } }