21 Commits

Author SHA1 Message Date
8b421b3cba michel 2025-10-08 17:37:40 +02:00
15a280ad2c conflit 2025-10-08 17:24:12 +02:00
4b7d0c7753 nul 2025-10-08 17:14:42 +02:00
2fe044753a rm out 2025-10-08 17:09:49 +02:00
2dc9caee23 Merge branch 'JANNAIRE' 2025-10-08 17:08:13 +02:00
01835f841a incroyable 2025-10-08 17:07:11 +02:00
d8647f43ac reparation peut etre 2025-10-08 17:02:53 +02:00
cc14043782 readme 2025-10-08 16:25:54 +02:00
d75f05bee0 ajout d'un bouton retour 2025-10-08 16:20:13 +02:00
64108a95e9 niveau 2025-10-08 16:15:19 +02:00
c0d2e0e5e2 Merge branch 'master' into DUCREUX 2025-10-08 15:48:04 +02:00
78333b0c32 Merge branch 'JANNAIRE' 2025-10-08 15:42:18 +02:00
5b7f341011 rm ou 2025-10-08 15:41:41 +02:00
2c611bc6fd ajout du score et du chrono plus doc 2025-10-08 15:41:08 +02:00
8b079bc103 reparation 2025-10-08 15:26:45 +02:00
790c3faff2 Merge branch 'JANNAIRE' 2025-10-08 15:19:06 +02:00
3f8a86866a rm 2025-10-08 15:17:55 +02:00
ee85d6f8f4 ajout score 2025-10-08 15:16:50 +02:00
d2dd0bb982 rm 2025-10-08 15:14:37 +02:00
485b63357e rm 2025-10-08 15:11:02 +02:00
ca5c12a7ba ajout du chrono et score 2025-10-08 15:04:37 +02:00
16 changed files with 387 additions and 124 deletions

View File

@@ -7,7 +7,18 @@ import java.util.Set;
/**
* Logique principale du jeu du pendu (back).
* Gère le mot, les lettres trouvées, et les conditions de victoire/défaite.
* Ajoute un score et un chronomètre.
*
* Règles de score (simples) :
* - Lettre correcte : +10 points
* - Lettre incorrecte: -5 points (le score ne descend pas sous 0)
* - Bonus victoire : nombre d'erreure restante sur 6 fois 10
* - Bonus temps : Chaque seconde restante avant 60s vaut 2 points si tu met plus de 60 secondes tu n'a pas de bonus
* - Défaite : pas de bonus
*
* Chronomètre :
* - Démarre à la création de la partie
* - S'arrête définitivement à la fin (victoire/défaite)
*/
public class Game {
private final String word;
@@ -16,21 +27,32 @@ public class Game {
private final int maxErrors;
private int errors;
// --- Score & chrono ---
private int score = 0;
private long startNano; // début de partie (System.nanoTime)
private long endNano = -1L; // fin (sinon -1 = en cours)
public Game(String word, int maxErrors) {
this.word = word.toLowerCase();
this.maxErrors = maxErrors;
this.startNano = System.nanoTime(); // démarre le chrono à la création
}
/** Tente une lettre et renvoie le résultat */
/** Tente une lettre et renvoie le résultat + ajuste le score */
public Result play(char letter) {
char c = Character.toLowerCase(letter);
if (all.contains(c)) return Result.ALREADY;
all.add(c);
if (word.indexOf(c) >= 0) {
correct.add(c);
addScore(10);
// Si la lettre trouvée fait gagner immédiatement, on finalise ici
if (isWin()) end(true);
return Result.HIT;
} else {
errors++;
addScore(-5);
if (isLose()) end(false);
return Result.MISS;
}
}
@@ -62,7 +84,7 @@ public class Game {
return errors >= maxErrors;
}
/** Renvoie le nombre d'erreurs actuelles */
/** Nombre d'erreurs actuelles */
public int getErrors() { return errors; }
/** Liste les lettres déjà essayées */
@@ -73,4 +95,38 @@ public class Game {
for (Character ch : sorted) out.add(String.valueOf(ch));
return out;
}
}
// ---------- Score & Chrono ----------
/** Retourne le score courant */
public int getScore() { return score; }
/** Secondes écoulées depuis le début (si finie, temps figé) */
public long getElapsedSeconds() {
long end = (endNano > 0L) ? endNano : System.nanoTime();
long deltaNs = end - startNano;
if (deltaNs < 0) deltaNs = 0;
return deltaNs / 1_000_000_000L;
}
/** Termine la partie (victoire/défaite) et applique le bonus si gagné */
public void end(boolean win) {
if (endNano > 0L) return; // déjà terminé
endNano = System.nanoTime();
if (win) {
int remaining = Math.max(0, maxErrors - errors);
int timeBonus = (int) Math.max(0, 60 - getElapsedSeconds()) * 2;
addScore(remaining * 10 + timeBonus);
}
}
// --- utilitaires privés ---
private void addScore(int delta) {
if (delta < 0) {
// lettre ratée => +1 erreur
errors++;
}
score += delta;
if (score < 0) score = 0;
}
}

View File

@@ -2,84 +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<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"
);
}
/** RNG partagé et cache des mots chargés. */
private static final SecureRandom RNG = new SecureRandom();
/** Cache mémoire + RNG. */
private static volatile List<String> 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.
*/
/** 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. */
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.
*/
/** 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();
}
/** Garantit que le cache est initialisé. */
// -------------------- internes --------------------
/** Charge le cache si nécessaire (thread-safe). */
private static void ensureLoaded() {
if (CACHE == null) {
synchronized (Words.class) {
if (CACHE == null) {
CACHE = loadFromFileOrDefault();
}
if (CACHE == null) CACHE = loadFromFileOrDefault();
}
}
}
/** Tente de charger depuis le fichier, sinon renvoie la liste par défaut. */
/** Tente chaque chemin candidat, sinon retourne DEFAULT mélangé. */
private static List<String> loadFromFileOrDefault() {
List<String> fromFile = readUtf8Lines(WORDS_PATH);
if (fromFile.isEmpty()) return DEFAULT;
return fromFile;
List<String> data = readFirstExistingCandidate();
if (data.isEmpty()) {
data = new ArrayList<>(DEFAULT);
}
Collections.shuffle(data, RNG); // casse tout déterminisme initial
return data;
}
/**
* 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
/** 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 result;
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

@@ -1,125 +1,234 @@
package front;
import back.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Interface graphique (Swing) du jeu du pendu.
* - Affiche une image différente à chaque erreur.
* - Champ de saisie pour entrer une lettre.
* - Texte pour le mot masqué et les lettres essayées.
* Interface graphique du pendu avec niveaux :
* - facile : mots < 8 lettres
* - moyen : mots ≥ 8 lettres
* - difficile : 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;
private JLabel wordLabel;
private JLabel triedLabel;
private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel;
private JTextField input;
private JButton tryBtn;
private JButton newGameBtn;
private JButton tryBtn, newBtn, menuBtn, quitBtn;
private Game game;
private String currentWord;
private List<String> words;
private int index = 0;
private final int level;
private String currentWord = "";
private Timer timer;
/** Affiche la fenêtre principale */
// Cumul de session (niveau difficile)
private long sessionStartNano = -1L;
private int sessionScore = 0;
/** Reçoit la difficulté (facile, moyen, difficile). */
public GameUI(int level) {
this.level = level;
}
/** Affiche la fenêtre et lance la session. */
public void show() {
setupUI();
startNewGame();
setupWindow();
setupLayout();
setupActions();
startNewSession();
frame.setVisible(true);
}
/** Initialise les composants Swing */
private void setupUI() {
/** 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). */
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);
frame.setSize(520, 520);
frame.setSize(560, 560);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout(12, 12));
}
// Image du pendu
/** Layout + composants. */
private void setupLayout() {
imgLabel = new JLabel("", SwingConstants.CENTER);
frame.add(imgLabel, BorderLayout.CENTER);
// Panneau bas: saisie + actions
wordLabel = new JLabel("Mot : ");
triedLabel = new JLabel("Lettres essayées : ");
scoreLabel = new JLabel("Score : 0");
timeLabel = new JLabel("Temps : 0s");
JLabel titleLabel = new JLabel("Sauver Michel!!", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 22));
titleLabel.setForeground(Color.RED);
JPanel top = new JPanel(new BorderLayout());
JPanel infoPanel = new JPanel(new GridLayout(2, 1));
infoPanel.add(buildTopLine(wordLabel, scoreLabel));
infoPanel.add(buildTopLine(triedLabel, timeLabel));
top.add(titleLabel, BorderLayout.NORTH);
top.add(infoPanel, BorderLayout.CENTER);
frame.add(top, BorderLayout.NORTH);
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");
quitBtn = new JButton("Quitter");
actionPanel.add(menuBtn);
actionPanel.add(quitBtn);
newGameBtn = new JButton("Nouvelle partie");
bottom.add(inputPanel, BorderLayout.WEST);
bottom.add(newGameBtn, BorderLayout.EAST);
bottom.add(actionPanel, BorderLayout.EAST);
frame.add(bottom, BorderLayout.SOUTH);
// Panneau haut: mot + lettres essayées
JPanel top = new JPanel();
top.setLayout(new BoxLayout(top, BoxLayout.Y_AXIS));
wordLabel = new JLabel("Mot : ");
triedLabel = new JLabel("Lettres essayées : ");
wordLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
triedLabel.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
top.add(wordLabel);
top.add(triedLabel);
frame.add(top, BorderLayout.NORTH);
// Actions
tryBtn.addActionListener(this::onTry);
input.addActionListener(this::onTry);
newGameBtn.addActionListener(e -> startNewGame());
}
/** Démarre une nouvelle partie */
private void startNewGame() {
currentWord = Words.random();
/** Ligne dinfos (gauche/droite). */
private JPanel buildTopLine(JLabel left, JLabel right) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
p.add(left);
p.add(Box.createHorizontalStrut(24));
p.add(right);
return p;
}
/** 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());
timer = new Timer(1000, e -> refreshStatsOnly());
}
/** Retour vers le menu de sélection. */
private void returnToMenu() {
timer.stop();
frame.dispose();
MenuUI menu = new MenuUI();
menu.show();
}
/** Démarre un nouveau mot (ou termine la session si plus de mots). */
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");
return;
}
currentWord = words.get(index++);
game = new Game(currentWord, 7);
input.setText("");
input.requestFocusInWindow();
if (!timer.isRunning()) timer.start();
refreshUI();
}
/** Gère le clic sur "Essayer" ou Entrée dans le champ */
/** Tente une lettre (bouton/Entrée). */
private void onTry(ActionEvent e) {
String text = input.getText();
if (!Check.isLetter(text)) {
JOptionPane.showMessageDialog(frame, "Tape une seule lettre (A-Z).");
showMsg("Tape une seule lettre (A-Z).");
input.requestFocusInWindow();
input.selectAll();
return;
}
char c = Character.toLowerCase(text.charAt(0));
Result res = game.play(c);
switch (res) {
case ALREADY: JOptionPane.showMessageDialog(frame, "Lettre déjà utilisée.");
break;
case HIT:
break;
case MISS:
break;
}
Result res = game.play(Character.toLowerCase(text.charAt(0)));
if (res == Result.ALREADY) showMsg("Lettre déjà utilisée.");
input.setText("");
refreshUI();
checkEnd();
}
if (game.isWin()) {
refreshUI();
JOptionPane.showMessageDialog(frame, "Bravo ! Le mot était : " + currentWord);
} else if (game.isLose()) {
refreshUI();
JOptionPane.showMessageDialog(frame, "Perdu ! Le mot était : " + currentWord);
/** Fin du mot → affiche popup et enchaîne (lvl 3 cumule score/chrono). */
private void checkEnd() {
if (!(game.isWin() || game.isLose())) return;
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");
if (level == 3 && index < words.size()) {
sessionScore += game.getScore();
startRound();
} else {
timer.stop();
}
}
/** Met à jour l'image et les textes selon l'état courant */
/** Refresh complet. */
private void refreshUI() {
imgLabel.setIcon(Gallows.icon(game.getErrors()));
wordLabel.setText("Mot : " + game.maskedWord());
triedLabel.setText("Lettres essayées : " + String.join(", ", game.triedLetters()));
refreshStatsOnly();
frame.repaint();
}
/** Refresh stats (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");
}
/** 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);
}
}

View File

@@ -0,0 +1,50 @@
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).
*/
public class MenuUI {
private JFrame frame;
/**
* Interface graphique de la page d'accueil du jeu du pendu.
*/
public void show() {
frame = new JFrame("Jeu du Pendu");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout(12, 12));
JLabel title = new JLabel("Choisis une difficulté", SwingConstants.CENTER);
title.setFont(new Font("Arial", Font.BOLD, 20));
frame.add(title, BorderLayout.NORTH);
JPanel buttons = new JPanel(new GridLayout(3, 1, 10, 10));
JButton easyBtn = new JButton("Niveau Facile");
JButton mediumBtn = new JButton("Niveau Moyen");
JButton hardBtn = new JButton("Niveau Difficile");
buttons.add(easyBtn);
buttons.add(mediumBtn);
buttons.add(hardBtn);
frame.add(buttons, BorderLayout.CENTER);
easyBtn.addActionListener(e -> startGame(1));
mediumBtn.addActionListener(e -> startGame(2));
hardBtn.addActionListener(e -> startGame(3));
frame.setVisible(true);
}
/** Lance le jeu avec le niveau choisi */
private void startGame(int level) {
frame.dispose(); // ferme le menu
GameUI ui = new GameUI(level);
ui.show();
}
}

View File

@@ -1,17 +1,16 @@
package main;
import front.GameUI;
import front.MenuUI;
/**
* Point d'entrée du programme.
* Lance l'interface graphique du jeu du pendu.
*/
* Point d'entrée du programme.
* Affiche le menu de sélection avant de lancer le jeu.
*/
public class Main {
public static void main(String[] args) {
// Démarre l'UI Swing sur le thread de l'EDT
javax.swing.SwingUtilities.invokeLater(() -> {
GameUI ui = new GameUI();
ui.show();
MenuUI menu = new MenuUI();
menu.show();
});
}
}

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.

Binary file not shown.