diff --git a/Jeu_pendu/Back/Game.java b/Jeu_pendu/Back/Game.java index 1e4354b..7026453 100644 --- a/Jeu_pendu/Back/Game.java +++ b/Jeu_pendu/Back/Game.java @@ -1,155 +1,132 @@ -package front; +package back; -import back.*; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** - * Interface graphique du jeu du pendu. - * (Toutes les méthodes ≤ 50 lignes) + * Logique principale du jeu du pendu (back). + * 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 : + (restant * 10) + bonus temps + * * bonus temps = max(0, 60 - secondes) * 2 (jusqu'à 120 pts si < 60s) + * - 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 GameUI { - private JFrame frame; - private JLabel imgLabel, wordLabel, triedLabel, scoreLabel, timeLabel; - private JTextField input; - private JButton tryBtn, newGameBtn; - private Game game; - private String currentWord; - private Timer timer; +public class Game { + private final String word; + private final Set correct = new HashSet<>(); + private final Set all = new HashSet<>(); + private final int maxErrors; + private int errors; - /** Lance la fenêtre et démarre une partie */ - public void show() { - setupWindow(); - setupLayout(); - setupActions(); - startNewGame(); - frame.setVisible(true); + // --- 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 } - /** Crée la fenêtre principale */ - private void setupWindow() { - frame = new JFrame("Jeu du Pendu"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(560, 560); - frame.setLocationRelativeTo(null); - frame.setLayout(new BorderLayout(12, 12)); - } + /** 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; - /** Construit les composants et le layout */ - private void setupLayout() { - imgLabel = new JLabel("", SwingConstants.CENTER); - frame.add(imgLabel, BorderLayout.CENTER); - - wordLabel = new JLabel("Mot : "); - triedLabel = new JLabel("Lettres essayées : "); - scoreLabel = new JLabel("Score : 0"); - timeLabel = new JLabel("Temps : 0s"); - - JPanel top = new JPanel(new GridLayout(2, 1)); - top.add(buildTopLine(wordLabel, scoreLabel)); - top.add(buildTopLine(triedLabel, timeLabel)); - 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"); - newGameBtn = new JButton("Nouvelle partie"); - inputPanel.add(new JLabel("Lettre :")); - inputPanel.add(input); - inputPanel.add(tryBtn); - bottom.add(inputPanel, BorderLayout.WEST); - bottom.add(newGameBtn, BorderLayout.EAST); - frame.add(bottom, BorderLayout.SOUTH); - } - - /** Crée une ligne du haut avec 2 labels */ - private JPanel buildTopLine(JLabel left, JLabel right) { - JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - panel.add(left); - panel.add(Box.createHorizontalStrut(24)); - panel.add(right); - return panel; - } - - /** Ajoute les actions et le timer */ - private void setupActions() { - tryBtn.addActionListener(this::onTry); - input.addActionListener(this::onTry); - newGameBtn.addActionListener(e -> startNewGame()); - timer = new Timer(1000, e -> refreshStatsOnly()); - } - - /** Démarre une nouvelle partie */ - private void startNewGame() { - currentWord = Words.random(); - game = new Game(currentWord, 7); - input.setText(""); - input.requestFocusInWindow(); - if (!timer.isRunning()) timer.start(); - refreshUI(); - } - - /** Gère le clic ou l'appui sur Entrée */ - private void onTry(ActionEvent e) { - String text = input.getText(); - if (!Check.isLetter(text)) { - showMsg("Tape une seule lettre (A-Z)."); - input.requestFocusInWindow(); - input.selectAll(); - return; - } - Result res = game.play(Character.toLowerCase(text.charAt(0))); - handleResult(res); - input.setText(""); - refreshUI(); - checkEnd(); - } - - /** Réagit selon le résultat d'une tentative */ - private void handleResult(Result res) { - switch (res) { - case ALREADY: - showMsg("Lettre déjà utilisée."); - break; - case HIT: - case MISS: - break; // rien, juste refresh + 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 { + addScore(-5); + if (isLose()) end(false); + return Result.MISS; } } - /** Vérifie si la partie est finie */ - private void checkEnd() { - if (game.isWin() || game.isLose()) { - timer.stop(); - game.end(game.isWin()); - String msg = (game.isWin() ? "Bravo !" : "Perdu !") - + " Le mot était : " + currentWord - + "\nScore final : " + game.getScore() - + "\nTemps : " + game.getElapsedSeconds() + "s"; - showMsg(msg); + /** Retourne le mot masqué avec les lettres trouvées */ + public String maskedWord() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (!Character.isLetter(c)) sb.append(c); + else if (correct.contains(c)) sb.append(c); + else sb.append('_'); + if (i < word.length() - 1) sb.append(' '); + } + return sb.toString(); + } + + /** Vérifie si le joueur a gagné */ + public boolean isWin() { + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (Character.isLetter(c) && !correct.contains(c)) return false; + } + return true; + } + + /** Vérifie si le joueur a perdu */ + public boolean isLose() { + return errors >= maxErrors; + } + + /** Nombre d'erreurs actuelles */ + public int getErrors() { return errors; } + + /** Liste les lettres déjà essayées */ + public List triedLetters() { + List sorted = new ArrayList<>(all); + sorted.sort(Character::compareTo); + List out = new ArrayList<>(); + 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); } } - /** Met à jour tout l'affichage */ - 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(); - } - - /** Met à jour uniquement score + chrono */ - private void refreshStatsOnly() { - scoreLabel.setText("Score : " + game.getScore()); - timeLabel.setText("Temps : " + game.getElapsedSeconds() + "s"); - } - - /** Affiche une boîte de message */ - private void showMsg(String msg) { - JOptionPane.showMessageDialog(frame, msg); + // --- utilitaires privés --- + private void addScore(int delta) { + if (delta < 0) { + // lettre ratée => +1 erreur + errors++; + } + score += delta; + if (score < 0) score = 0; } } diff --git a/Jeu_pendu/Front/GameUI.java b/Jeu_pendu/Front/GameUI.java index b973411..1e4354b 100644 --- a/Jeu_pendu/Front/GameUI.java +++ b/Jeu_pendu/Front/GameUI.java @@ -1,76 +1,85 @@ package front; import back.*; + import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; /** - * 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 jeu du pendu. + * (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, newGameBtn; private Game game; private String currentWord; + private Timer timer; - /** Affiche la fenêtre principale */ + /** Lance la fenêtre et démarre une partie */ public void show() { - setupUI(); + setupWindow(); + setupLayout(); + setupActions(); startNewGame(); frame.setVisible(true); } - /** Initialise les composants Swing */ - private void setupUI() { + /** Crée la 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 + /** Construit les composants et le layout */ + 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"); + + JPanel top = new JPanel(new GridLayout(2, 1)); + top.add(buildTopLine(wordLabel, scoreLabel)); + top.add(buildTopLine(triedLabel, timeLabel)); + 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"); + newGameBtn = new JButton("Nouvelle partie"); inputPanel.add(new JLabel("Lettre :")); inputPanel.add(input); inputPanel.add(tryBtn); - - newGameBtn = new JButton("Nouvelle partie"); bottom.add(inputPanel, BorderLayout.WEST); bottom.add(newGameBtn, 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); + /** Crée une ligne du haut avec 2 labels */ + private JPanel buildTopLine(JLabel left, JLabel right) { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + panel.add(left); + panel.add(Box.createHorizontalStrut(24)); + panel.add(right); + return panel; + } - // Actions + /** Ajoute les actions et le timer */ + private void setupActions() { tryBtn.addActionListener(this::onTry); input.addActionListener(this::onTry); newGameBtn.addActionListener(e -> startNewGame()); + timer = new Timer(1000, e -> refreshStatsOnly()); } /** Démarre une nouvelle partie */ @@ -79,47 +88,68 @@ public class GameUI { 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 */ + /** Gère le clic ou l'appui sur 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))); + handleResult(res); 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); + /** Réagit selon le résultat d'une tentative */ + private void handleResult(Result res) { + switch (res) { + case ALREADY: + showMsg("Lettre déjà utilisée."); + break; + case HIT: + case MISS: + break; // rien, juste refresh } } - /** Met à jour l'image et les textes selon l'état courant */ + /** Vérifie si la partie est finie */ + private void checkEnd() { + if (game.isWin() || game.isLose()) { + timer.stop(); + game.end(game.isWin()); + String msg = (game.isWin() ? "Bravo !" : "Perdu !") + + " Le mot était : " + currentWord + + "\nScore final : " + game.getScore() + + "\nTemps : " + game.getElapsedSeconds() + "s"; + showMsg(msg); + } + } + + /** Met à jour tout l'affichage */ 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(); } -} \ No newline at end of file + + /** Met à jour uniquement score + chrono */ + private void refreshStatsOnly() { + scoreLabel.setText("Score : " + game.getScore()); + timeLabel.setText("Temps : " + game.getElapsedSeconds() + "s"); + } + + /** Affiche une boîte de message */ + private void showMsg(String msg) { + JOptionPane.showMessageDialog(frame, msg); + } +} diff --git a/TD3 - DEV5.1.pdf b/TD3 - DEV5.1.pdf index 7a69e5a..509685d 100644 Binary files a/TD3 - DEV5.1.pdf and b/TD3 - DEV5.1.pdf differ