2 Commits

Author SHA1 Message Date
01835f841a incroyable 2025-10-08 17:07:11 +02:00
d8647f43ac reparation peut etre 2025-10-08 17:02:53 +02:00
23 changed files with 179 additions and 146 deletions

View File

@@ -2,94 +2,129 @@ package back;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
/** /**
* Fournit les mots pour le jeu. * Gère la bibliothèque de mots (chargement + sélection aléatoire).
* - Lit d'abord "bibliothèque/mots.txt" (UTF-8), 1 mot par ligne. * - Lit "bibliothèque/mots.txt" OU "Bibliotheque/mots.txt" (UTF-8), 1 mot par ligne.
* - Ignore les lignes vides et celles qui commencent par '#'. * - Ignore lignes vides et commentaires (#...).
* - Si le fichier est introuvable ou vide, bascule sur une liste par défaut. * - Fournit des helpers pour tirer des mots selon la difficulté.
*/ */
public class Words { public class Words {
/** 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 le fichier n'est pas disponible. */ /** Chemins possibles (accents/casse) pour maximiser la compatibilité. */
private static final List<String> DEFAULT = List.of( private static final Path[] CANDIDATES = new Path[] {
"algorithm", "variable", "function", "interface", "inheritance", Paths.get("bibliothèque", "mots.txt"),
"exception", "compiler", "database", "network", "architecture", Paths.get("Bibliotheque", "mots.txt")
"iteration", "recursion", "encryption", "framework", "protocol" };
);
/** RNG partagé et cache des mots chargés. */ /** Liste de secours si aucun fichier trouvé/valide. */
private static final SecureRandom RNG = new SecureRandom(); 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; private static volatile List<String> CACHE = null;
private static final SecureRandom RNG = new SecureRandom();
/** /** Renvoie la liste complète (copie) — charge une seule fois si nécessaire. */
* 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<String> loadFromFileOrDefault() {
List<String> 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<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 result;
}
/** Retourne la liste complète des mots disponibles */
public static List<String> all() { public static List<String> all() {
ensureLoaded(); ensureLoaded();
return new ArrayList<>(CACHE); return new ArrayList<>(CACHE);
} }
/** Petit utilitaire pour afficher un mot masqué dans les messages */ /** Renvoie un mot aléatoire dans tout le dictionnaire. */
public static String hiddenWord(Game g) { public static String random() {
return g.maskedWord().replace(' ', '_'); 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). */
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<String> loadFromFileOrDefault() {
List<String> 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<String> readFirstExistingCandidate() {
for (Path p : CANDIDATES) {
List<String> list = readUtf8Trimmed(p);
if (!list.isEmpty()) return list;
}
return new ArrayList<>();
}
/** 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;
}
}

View File

@@ -9,65 +9,67 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Interface graphique du jeu du pendu avec gestion des difficultés. * Interface graphique du pendu avec niveaux :
* - Niveau Facile : mots < 8 lettres * - 1 : mots < 8 lettres
* - Niveau Moyen : mots ≥ 8 lettres * - 2 : mots ≥ 8 lettres
* - Niveau Difficile : deux mots à la suite (score & chrono cumulés) * - 3 : deux mots (score + chrono cumulés)
* Toutes les méthodes ≤ 50 lignes. * Boutons : Essayer / Nouvelle partie / Menu / Quitter.
* (Toutes les méthodes ≤ 50 lignes)
*/ */
public class GameUI { public class GameUI {
private JFrame frame; private JFrame frame;
private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel; private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel;
private JTextField input; private JTextField input;
private JButton tryBtn, quitBtn, menuBtn, newGameBtn; private JButton tryBtn, newBtn, menuBtn, quitBtn;
private Game game; private Game game;
private List<String> words; private List<String> words;
private int index = 0; private int index = 0;
private int level; private final int level;
private String currentWord = ""; private String currentWord = "";
private Timer timer; private Timer timer;
// Cumul pour le niveau 3 // Cumul de session (niveau 3)
private long sessionStartNano = -1L; // début du niveau private long sessionStartNano = -1L;
private int sessionScore = 0; // score cumulé des mots terminés private int sessionScore = 0;
public GameUI(int level) { this.level = level; } /** 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() { public void show() {
setupWindow(); setupWindow();
setupLayout(); setupLayout();
setupActions(); setupActions();
prepareWords(); startNewSession();
sessionStartNano = System.nanoTime(); // CHRONO DE SESSION (lvl 3)
startRound();
frame.setVisible(true); frame.setVisible(true);
} }
/** Prépare la liste des mots selon la difficulté choisie */ /** Démarre une nouvelle session (nouveaux mots, reset chrono/score session). */
private void prepareWords() { private void startNewSession() {
words = new ArrayList<String>(); sessionStartNano = System.nanoTime();
List<String> all = Words.all(); sessionScore = 0;
index = 0;
if (level == 1) { prepareWords();
for (String w : all) if (w.length() < 8) words.add(w); startRound();
} else if (level == 2) {
for (String w : all) if (w.length() >= 8) words.add(w);
} else if (level == 3) {
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());
} }
/** Crée la fenêtre principale */ /** Ppare 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() { private void setupWindow() {
frame = new JFrame("Jeu du Pendu"); frame = new JFrame("Jeu du Pendu");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -76,7 +78,7 @@ public class GameUI {
frame.setLayout(new BorderLayout(12, 12)); frame.setLayout(new BorderLayout(12, 12));
} }
/** Construit la mise en page et les composants */ /** Layout + composants. */
private void setupLayout() { private void setupLayout() {
imgLabel = new JLabel("", SwingConstants.CENTER); imgLabel = new JLabel("", SwingConstants.CENTER);
frame.add(imgLabel, BorderLayout.CENTER); frame.add(imgLabel, BorderLayout.CENTER);
@@ -95,10 +97,11 @@ public class GameUI {
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
input = new JTextField(5); input = new JTextField(5);
tryBtn = new JButton("Essayer"); tryBtn = new JButton("Essayer");
newGameBtn = new JButton("Nouvelle partie"); newBtn = new JButton("Nouvelle partie");
inputPanel.add(new JLabel("Lettre :")); inputPanel.add(new JLabel("Lettre :"));
inputPanel.add(input); inputPanel.add(input);
inputPanel.add(tryBtn); inputPanel.add(tryBtn);
inputPanel.add(newBtn);
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
menuBtn = new JButton("Menu"); menuBtn = new JButton("Menu");
@@ -108,11 +111,10 @@ public class GameUI {
bottom.add(inputPanel, BorderLayout.WEST); bottom.add(inputPanel, BorderLayout.WEST);
bottom.add(actionPanel, BorderLayout.EAST); bottom.add(actionPanel, BorderLayout.EAST);
top.add(newGameBtn, BorderLayout.EAST);
frame.add(bottom, BorderLayout.SOUTH); frame.add(bottom, BorderLayout.SOUTH);
} }
/** Construit une ligne (label gauche + espace + label droit) */ /** Ligne dinfos (gauche/droite). */
private JPanel buildTopLine(JLabel left, JLabel right) { private JPanel buildTopLine(JLabel left, JLabel right) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
p.add(left); p.add(left);
@@ -121,16 +123,17 @@ public class GameUI {
return p; return p;
} }
/** Branche les actions + timer */ /** Actions + timer. */
private void setupActions() { private void setupActions() {
tryBtn.addActionListener(this::onTry); tryBtn.addActionListener(this::onTry);
input.addActionListener(this::onTry); input.addActionListener(this::onTry);
quitBtn.addActionListener(e -> frame.dispose()); newBtn.addActionListener(e -> startNewSession());
menuBtn.addActionListener(e -> returnToMenu()); menuBtn.addActionListener(e -> returnToMenu());
quitBtn.addActionListener(e -> frame.dispose());
timer = new Timer(1000, e -> refreshStatsOnly()); timer = new Timer(1000, e -> refreshStatsOnly());
} }
/** Retour au menu principal */ /** Retour vers le menu de sélection. */
private void returnToMenu() { private void returnToMenu() {
timer.stop(); timer.stop();
frame.dispose(); frame.dispose();
@@ -138,14 +141,12 @@ public class GameUI {
menu.show(); menu.show();
} }
/** Démarre un nouveau mot (ou termine la séquence au niveau 3) */ /** Démarre un nouveau mot (ou termine la session si plus de mots). */
private void startRound() { private void startRound() {
if (index >= words.size()) { if (index >= words.size()) {
// Fin du niveau (affiche score cumulé si lvl 3) int total = (level == 3) ? sessionScore : game.getScore();
int finalScore = (level == 3) ? sessionScore : game.getScore();
long secs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds(); long secs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
showMsg("Niveau terminé !\nScore total : " + finalScore + "\nTemps : " + secs + "s"); showMsg("Niveau terminé !\nScore total : " + total + "\nTemps : " + secs + "s");
frame.dispose();
return; return;
} }
currentWord = words.get(index++); currentWord = words.get(index++);
@@ -156,7 +157,7 @@ public class GameUI {
refreshUI(); refreshUI();
} }
/** Tente une lettre (clic bouton ou Entrée) */ /** Tente une lettre (bouton/Entrée). */
private void onTry(ActionEvent e) { private void onTry(ActionEvent e) {
String text = input.getText(); String text = input.getText();
if (!Check.isLetter(text)) { if (!Check.isLetter(text)) {
@@ -172,29 +173,28 @@ public class GameUI {
checkEnd(); checkEnd();
} }
/** Vérifie fin de mot et enchaîne si besoin (niveau 3 cumule score & chrono) */ /** Fin du mot → affiche popup et enchaîne (lvl 3 cumule score/chrono). */
private void checkEnd() { private void checkEnd() {
if (!(game.isWin() || game.isLose())) return; if (!(game.isWin() || game.isLose())) return;
game.end(game.isWin()); // applique bonus de fin pour ce mot game.end(game.isWin());
int displayScore = (level == 3) ? sessionScore + game.getScore() : game.getScore(); int displayScore = (level == 3) ? sessionScore + game.getScore() : game.getScore();
long displaySecs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds(); long displaySecs = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
String verdict = game.isWin() ? "Bravo !" : "Perdu !"; showMsg((game.isWin() ? "Bravo !" : "Perdu !")
showMsg(verdict + " Le mot était : " + currentWord + " Le mot était : " + currentWord
+ "\nScore : " + displayScore + "\nScore : " + displayScore
+ "\nTemps : " + displaySecs + "s"); + "\nTemps : " + displaySecs + "s");
if (level == 3 && index < words.size()) { if (level == 3 && index < words.size()) {
// CUMULE le score du mot terminé, poursuit SANS réinitialiser le chrono
sessionScore += game.getScore(); sessionScore += game.getScore();
startRound(); startRound();
} else { } else {
timer.stop(); // fins niveaux 1/2, ou mot 2 du niveau 3 timer.stop();
} }
} }
/** Rafraîchit image + textes + stats */ /** Refresh complet. */
private void refreshUI() { private void refreshUI() {
imgLabel.setIcon(Gallows.icon(game.getErrors())); imgLabel.setIcon(Gallows.icon(game.getErrors()));
wordLabel.setText("Mot : " + game.maskedWord()); wordLabel.setText("Mot : " + game.maskedWord());
@@ -203,7 +203,7 @@ public class GameUI {
frame.repaint(); frame.repaint();
} }
/** Rafraîchit uniquement score + chrono (cumul si niveau 3) */ /** Refresh stats (score/chrono). */
private void refreshStatsOnly() { private void refreshStatsOnly() {
int s = (level == 3) ? sessionScore + game.getScore() : game.getScore(); int s = (level == 3) ? sessionScore + game.getScore() : game.getScore();
long t = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds(); long t = (level == 3) ? getSessionSeconds() : game.getElapsedSeconds();
@@ -211,7 +211,7 @@ public class GameUI {
timeLabel.setText("Temps : " + t + "s"); timeLabel.setText("Temps : " + t + "s");
} }
/** Durée écoulée depuis le début du niveau (niveau 3) */ /** Secondes écoulées depuis le début de la session (niveau 3). */
private long getSessionSeconds() { private long getSessionSeconds() {
long now = System.nanoTime(); long now = System.nanoTime();
long delta = now - sessionStartNano; long delta = now - sessionStartNano;
@@ -219,6 +219,8 @@ public class GameUI {
return delta / 1_000_000_000L; return delta / 1_000_000_000L;
} }
/** Affiche un message */ /** Popup info. */
private void showMsg(String msg) { JOptionPane.showMessageDialog(frame, msg); } private void showMsg(String msg) {
} JOptionPane.showMessageDialog(frame, msg);
}
}

View File

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

Binary file not shown.

View File

@@ -5,15 +5,11 @@
## Fonctionnalités ## Fonctionnalités
- Affichage dune **image différente du pendu** à chaque erreur Affichage dune **image différente du pendu** à chaque erreur
- Lecture de mots depuis un **fichier externe** (`bibliothèque/mots.txt`) Lecture de mots depuis un **fichier externe** (`bibliothèque/mots.txt`)
- Validation des entrées (une seule LETTRE à la fois) Validation des entrées (une seule LETTRE à la fois)
- Bouton **“Nouvelle partie”** pour rejouer sans relancer le programme Bouton **“Nouvelle partie”** pour rejouer sans relancer le programme
- Bouton **“Quitter”** pour quitter le programme Messages de victoire / défaite
- 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)
--- ---

BIN
out/back/Check.class Normal file

Binary file not shown.

BIN
out/back/Game.class Normal file

Binary file not shown.

BIN
out/back/Result.class Normal file

Binary file not shown.

BIN
out/back/Words.class Normal file

Binary file not shown.

Binary file not shown.

BIN
out/front/Gallows.class Normal file

Binary file not shown.

BIN
out/front/GameUI$1.class Normal file

Binary file not shown.

BIN
out/front/GameUI.class Normal file

Binary file not shown.

BIN
out/main/Main.class Normal file

Binary file not shown.