2 Commits

Author SHA1 Message Date
4b7d0c7753 nul 2025-10-08 17:14:42 +02:00
cc14043782 readme 2025-10-08 16:25:54 +02:00
23 changed files with 124 additions and 157 deletions

View File

@@ -2,129 +2,94 @@ 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 {
/** 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<String> 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<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"
);
}
/** 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 dexé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);
}
} catch (IOException ignored) {
// on tombera sur DEFAULT
}
return out;
/** Retourne la liste complète des mots disponibles */
public static List<String> 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(' ', '_');
}
}

View File

@@ -9,67 +9,65 @@ 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 Facile : mots < 8 lettres
* - Niveau Moyen : mots ≥ 8 lettres
* - Niveau Difficile : deux mots à la suite (score & chrono cumulés)
* 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, newGameBtn;
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;
// Cumul pour le niveau 3
private long sessionStartNano = -1L; // début du niveau
private int sessionScore = 0; // score cumulé des mots terminés
/** Reçoit la difficulté (1, 2, 3). */
public GameUI(int level) {
this.level = level;
}
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();
sessionStartNano = System.nanoTime(); // CHRONO DE SESSION (lvl 3)
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<>();
words = new ArrayList<String>();
List<String> all = Words.all();
if (level == 1) {
words.add(Words.randomShortWord());
for (String w : all) if (w.length() < 8) words.add(w);
} else if (level == 2) {
words.add(Words.randomLongWord());
for (String w : all) if (w.length() >= 8) words.add(w);
} else if (level == 3) {
words = Words.randomPair(); // [court, long] aléatoires
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()); // filet de sécurité
if (words.isEmpty()) words.add(Words.random());
}
/** Fenêtre principale. */
/** Crée la fenêtre principale */
private void setupWindow() {
frame = new JFrame("Jeu du Pendu");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -78,7 +76,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);
@@ -97,11 +95,10 @@ public class GameUI {
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
input = new JTextField(5);
tryBtn = new JButton("Essayer");
newBtn = new JButton("Nouvelle partie");
newGameBtn = 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");
@@ -111,10 +108,11 @@ public class GameUI {
bottom.add(inputPanel, BorderLayout.WEST);
bottom.add(actionPanel, BorderLayout.EAST);
top.add(newGameBtn, BorderLayout.EAST);
frame.add(bottom, BorderLayout.SOUTH);
}
/** Ligne dinfos (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 +121,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 +138,14 @@ 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 la séquence au niveau 3) */
private void startRound() {
if (index >= words.size()) {
int total = (level == 3) ? sessionScore : game.getScore();
// Fin du niveau (affiche score cumulé si lvl 3)
int finalScore = (level == 3) ? sessionScore : game.getScore();
long secs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
showMsg("Niveau terminé !\nScore total : " + total + "\nTemps : " + secs + "s");
showMsg("Niveau terminé !\nScore total : " + finalScore + "\nTemps : " + secs + "s");
frame.dispose();
return;
}
currentWord = words.get(index++);
@@ -157,7 +156,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 +172,29 @@ public class GameUI {
checkEnd();
}
/** Fin du mot → affiche popup et enchaîne (lvl 3 cumule score/chrono). */
/** Vérifie fin de mot et enchaîne si besoin (niveau 3 cumule score & chrono) */
private void checkEnd() {
if (!(game.isWin() || game.isLose())) return;
game.end(game.isWin());
game.end(game.isWin()); // applique bonus de fin pour ce mot
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
String verdict = game.isWin() ? "Bravo !" : "Perdu !";
showMsg(verdict + " Le mot était : " + currentWord
+ "\nScore : " + displayScore
+ "\nTemps : " + displaySecs + "s");
if (level == 3 && index < words.size()) {
// CUMULE le score du mot terminé, poursuit SANS réinitialiser le chrono
sessionScore += game.getScore();
startRound();
} else {
timer.stop();
timer.stop(); // fins niveaux 1/2, ou mot 2 du niveau 3
}
}
/** Refresh complet. */
/** Rafraîchit image + textes + stats */
private void refreshUI() {
imgLabel.setIcon(Gallows.icon(game.getErrors()));
wordLabel.setText("Mot : " + game.maskedWord());
@@ -203,7 +203,7 @@ public class GameUI {
frame.repaint();
}
/** Refresh stats (score/chrono). */
/** Rafraîchit uniquement score + chrono (cumul si niveau 3) */
private void refreshStatsOnly() {
int s = (level == 3) ? sessionScore + game.getScore() : game.getScore();
long t = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
@@ -211,7 +211,7 @@ public class GameUI {
timeLabel.setText("Temps : " + t + "s");
}
/** Secondes écoulées depuis le début de la session (niveau 3). */
/** Durée écoulée depuis le début du niveau (niveau 3) */
private long getSessionSeconds() {
long now = System.nanoTime();
long delta = now - sessionStartNano;
@@ -219,8 +219,6 @@ public class GameUI {
return delta / 1_000_000_000L;
}
/** Popup info. */
private void showMsg(String msg) {
JOptionPane.showMessageDialog(frame, msg);
}
}
/** Affiche un message */
private void showMsg(String msg) { JOptionPane.showMessageDialog(frame, msg); }
}

View File

@@ -3,7 +3,7 @@ package front;
import javax.swing.*;
import java.awt.*;
/*
/**
* Menu de démarrage du jeu du pendu.
* Permet de choisir la difficulté (facile, moyen ou difficile).
*/
@@ -41,7 +41,7 @@ public class MenuUI {
frame.setVisible(true);
}
/* Lance le jeu avec le niveau choisi */
/** Lance le jeu avec le niveau choisi */
private void startGame(int level) {
frame.dispose(); // ferme le menu
GameUI ui = new GameUI(level);

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.

View File

@@ -5,11 +5,15 @@
## Fonctionnalités
Affichage dune **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 dune **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
- Bouton **“Quitter”** pour quitter le programme
- Bouton **“Menu”** pour retourner à la page menu et pouvoir rechoisir le niveau de difficulté
- Messages de victoire / défaite
- Score + Chronomètre en direct
- Sélection du niveau dans le menu avant de jouer (Facile, Moyen, Difficile)
---

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.