forked from menault/TD3_DEV51_Qualite_Algo
Compare commits
20 Commits
ba7e16dc64
...
dick
| Author | SHA1 | Date | |
|---|---|---|---|
| e649d30b33 | |||
| 584e8dc3c6 | |||
| 82b4e0ffd1 | |||
| 9cb9088b81 | |||
| 77b3158262 | |||
| 0f7c1f6b71 | |||
| 87909bbe35 | |||
| 9d7c681ca3 | |||
| e6e0d4cf71 | |||
| ffbe1e232e | |||
| a3627d9023 | |||
| 2a8eac0720 | |||
| 7206dca3ef | |||
| 0fa7ec527f | |||
| f7aecb038d | |||
| 62f6f94be7 | |||
| 90874a3296 | |||
| 49e32b316e | |||
| 06daa4e478 | |||
| 35c9fcd873 |
32
Makefile
32
Makefile
@@ -15,21 +15,45 @@ JCFLAGS = -encoding UTF-8 -implicit:none -cp $(OUT) -d $(OUT)
|
|||||||
CLASSFILES = Pendu.class \
|
CLASSFILES = Pendu.class \
|
||||||
Partie.class \
|
Partie.class \
|
||||||
Fenetre.class \
|
Fenetre.class \
|
||||||
Dessin.class
|
Dessin.class \
|
||||||
|
Mots.class \
|
||||||
|
Event.class \
|
||||||
|
LetterInputFilter.class \
|
||||||
|
MenuDifficulte.class \
|
||||||
|
Chronometre.class \
|
||||||
|
Score.class
|
||||||
|
|
||||||
# Dépendances
|
# Dépendances
|
||||||
$(OUT)Pendu.class : $(IN)Pendu.java $(OUT)Partie.class $(OUT)Fenetre.class
|
$(OUT)Pendu.class : $(IN)Pendu.java $(OUT)Partie.class $(OUT)Fenetre.class $(OUT)Event.class $(OUT)MenuDifficulte.class $(OUT)Score.class
|
||||||
$(JC) $(JCFLAGS) $<
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
$(OUT)Partie.class : $(IN)Partie.java
|
$(OUT)Partie.class : $(IN)Partie.java $(OUT)Mots.class
|
||||||
$(JC) $(JCFLAGS) $<
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
$(OUT)Fenetre.class : $(IN)Fenetre.java $(OUT)Partie.class $(OUT)Dessin.class
|
$(OUT)Fenetre.class : $(IN)Fenetre.java $(OUT)Partie.class $(OUT)Dessin.class $(OUT)Chronometre.class $(OUT)Score.class
|
||||||
$(JC) $(JCFLAGS) $<
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
$(OUT)Dessin.class : $(IN)Dessin.java
|
$(OUT)Dessin.class : $(IN)Dessin.java
|
||||||
$(JC) $(JCFLAGS) $<
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)Mots.class : $(IN)Mots.java
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)Event.class : $(IN)Event.java $(OUT)Fenetre.class $(OUT)LetterInputFilter.class
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)LetterInputFilter.class : $(IN)LetterInputFilter.java $(OUT)Fenetre.class
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)MenuDifficulte.class : $(IN)MenuDifficulte.java
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)Chronometre.class : $(IN)Chronometre.java
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
|
$(OUT)Score.class : $(IN)Score.java
|
||||||
|
$(JC) $(JCFLAGS) $<
|
||||||
|
|
||||||
# Commandes
|
# Commandes
|
||||||
Pendu : $(OUT)Pendu.class
|
Pendu : $(OUT)Pendu.class
|
||||||
|
|
||||||
|
|||||||
83
src/Chronometre.java
Normal file
83
src/Chronometre.java
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composant chronomètre (mm:ss) pour le jeu du pendu.
|
||||||
|
* - Démarre/stoppe/réinitialise le temps
|
||||||
|
* - S'affiche comme une barre en haut de la fenêtre
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @author Adrien
|
||||||
|
* Date : 08-10-2025
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public class Chronometre extends JPanel implements ActionListener {
|
||||||
|
|
||||||
|
private final JLabel timeLabel = new JLabel("00:00", SwingConstants.CENTER);
|
||||||
|
private final Timer timer; // tick chaque seconde
|
||||||
|
private boolean running = false;
|
||||||
|
private long startMillis = 0L;
|
||||||
|
private long accumulatedMillis = 0L;
|
||||||
|
|
||||||
|
public Chronometre() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||||
|
timeLabel.setFont(new Font("Segoe UI", Font.BOLD, 20));
|
||||||
|
add(timeLabel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Timer Swing (1s)
|
||||||
|
timer = new Timer(1000, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Démarre le chronomètre (ou reprend si en pause). */
|
||||||
|
public void start() {
|
||||||
|
if (!running) {
|
||||||
|
running = true;
|
||||||
|
startMillis = System.currentTimeMillis();
|
||||||
|
timer.start();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Met en pause le chronomètre. */
|
||||||
|
public void stop() {
|
||||||
|
if (running) {
|
||||||
|
running = false;
|
||||||
|
accumulatedMillis += System.currentTimeMillis() - startMillis;
|
||||||
|
timer.stop();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remet le chronomètre à 00:00 (sans le relancer). */
|
||||||
|
public void reset() {
|
||||||
|
running = false;
|
||||||
|
startMillis = 0L;
|
||||||
|
accumulatedMillis = 0L;
|
||||||
|
timer.stop();
|
||||||
|
updateLabel(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Temps écoulé total (en millisecondes). */
|
||||||
|
public long getElapsedMillis() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long runningMillis = running ? (now - startMillis) : 0L;
|
||||||
|
return accumulatedMillis + runningMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tick du timer : met à jour l'affichage. */
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
|
updateLabel(getElapsedMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- interne ---
|
||||||
|
private void updateLabel(long elapsedMillis) {
|
||||||
|
long totalSeconds = elapsedMillis / 1000;
|
||||||
|
long minutes = totalSeconds / 60;
|
||||||
|
long seconds = totalSeconds % 60;
|
||||||
|
timeLabel.setText(String.format("%02d:%02d", minutes, seconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/Dessin.java
128
src/Dessin.java
@@ -2,93 +2,101 @@ import javax.swing.*;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>Dessin</code> gère uniquement le dessin du pendu
|
* La classe <code>Dessin</code> gère uniquement le dessin du pendu,
|
||||||
|
* avec révélation progressive en fonction du nombre d'erreurs (stage).
|
||||||
*
|
*
|
||||||
* @version 0.1
|
* @version 1.1
|
||||||
* @author Adrien
|
* author Adrien
|
||||||
* Date : 08-10-2025
|
* Date : 08-10-2025
|
||||||
* Licence :
|
|
||||||
*/
|
*/
|
||||||
public class Dessin extends JPanel {
|
public class Dessin extends JPanel {
|
||||||
|
|
||||||
|
/** Nombre d'étapes max pour le personnage (hors potence). */
|
||||||
|
public static final int MAX_STAGE = 6;
|
||||||
|
|
||||||
|
/** Étape actuelle (erreurs) : 0..6 */
|
||||||
|
private int stage = 0;
|
||||||
|
|
||||||
// --- Constructeur ---
|
// --- Constructeur ---
|
||||||
public Dessin() {
|
public Dessin() {
|
||||||
// Taille préférée pour s'intégrer dans Fenetre
|
|
||||||
setPreferredSize(new Dimension(600, 350));
|
setPreferredSize(new Dimension(600, 350));
|
||||||
setBackground(new Color(245, 245, 245));
|
setBackground(new Color(245, 245, 245));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Définit l'étape (nb d'erreurs) et redessine. */
|
||||||
|
public void setStage(int newStage) {
|
||||||
|
int clamped = Math.max(0, Math.min(newStage, MAX_STAGE));
|
||||||
|
if (clamped != this.stage) {
|
||||||
|
this.stage = clamped;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStage() { return stage; }
|
||||||
|
|
||||||
// --- Dessin principal ---
|
// --- Dessin principal ---
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics graphics) {
|
protected void paintComponent(Graphics graphics) {
|
||||||
super.paintComponent(graphics);
|
super.paintComponent(graphics);
|
||||||
|
|
||||||
// Anti-aliasing pour des traits plus doux
|
Graphics2D g2 = (Graphics2D) graphics.create();
|
||||||
Graphics2D graphics2D = (Graphics2D) graphics.create();
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2.setStroke(new BasicStroke(3f));
|
||||||
graphics2D.setStroke(new BasicStroke(3f));
|
g2.setColor(Color.DARK_GRAY);
|
||||||
graphics2D.setColor(Color.DARK_GRAY);
|
|
||||||
|
|
||||||
// Repères et proportions
|
// Repères et proportions
|
||||||
int width = getWidth();
|
int width = getWidth();
|
||||||
int height = getHeight();
|
int height = getHeight();
|
||||||
int marginPixels = Math.min(width, height) / 12; // marge proportionnelle
|
int margin = Math.min(width, height) / 12;
|
||||||
|
|
||||||
// Potence : socle
|
// Potence (toujours affichée)
|
||||||
int baseYCoordinate = height - marginPixels;
|
int baseY = height - margin;
|
||||||
graphics2D.drawLine(marginPixels, baseYCoordinate, width / 2, baseYCoordinate);
|
g2.drawLine(margin, baseY, width / 2, baseY); // socle
|
||||||
|
int postX = margin + (width / 12);
|
||||||
// Montant vertical
|
g2.drawLine(postX, baseY, postX, margin); // montant
|
||||||
int postXCoordinate = marginPixels + (width / 12);
|
|
||||||
graphics2D.drawLine(postXCoordinate, baseYCoordinate, postXCoordinate, marginPixels);
|
|
||||||
|
|
||||||
// Traverse horizontale
|
|
||||||
int beamLength = width / 3;
|
int beamLength = width / 3;
|
||||||
graphics2D.drawLine(postXCoordinate, marginPixels, postXCoordinate + beamLength, marginPixels);
|
g2.drawLine(postX, margin, postX + beamLength, margin); // traverse
|
||||||
|
g2.drawLine(postX, margin + height / 10, postX + width / 12, margin); // renfort
|
||||||
|
|
||||||
// Renfort diagonal
|
// Corde (toujours)
|
||||||
graphics2D.drawLine(postXCoordinate, marginPixels + height / 10,
|
int ropeX = postX + beamLength;
|
||||||
postXCoordinate + width / 12, marginPixels);
|
int ropeTopY = margin;
|
||||||
|
int ropeBottomY = margin + height / 12;
|
||||||
|
g2.drawLine(ropeX, ropeTopY, ropeX, ropeBottomY);
|
||||||
|
|
||||||
// Corde
|
// Géométrie du personnage
|
||||||
int ropeXCoordinate = postXCoordinate + beamLength;
|
int headR = Math.min(width, height) / 16;
|
||||||
int ropeTopYCoordinate = marginPixels;
|
int headCX = ropeX;
|
||||||
int ropeBottomYCoordinate = marginPixels + height / 12;
|
int headCY = ropeBottomY + headR;
|
||||||
graphics2D.drawLine(ropeXCoordinate, ropeTopYCoordinate, ropeXCoordinate, ropeBottomYCoordinate);
|
int bodyTopY = headCY + headR;
|
||||||
|
int bodyBottomY = bodyTopY + height / 6;
|
||||||
|
int armSpan = width / 10;
|
||||||
|
int shouldersY = bodyTopY + height / 24;
|
||||||
|
int legSpan = width / 12;
|
||||||
|
|
||||||
// Personnage : tête
|
// Étapes du personnage
|
||||||
int headRadiusPixels = Math.min(width, height) / 16;
|
if (stage >= 1) { // tête
|
||||||
int headCenterX = ropeXCoordinate;
|
g2.drawOval(headCX - headR, headCY - headR, headR * 2, headR * 2);
|
||||||
int headCenterY = ropeBottomYCoordinate + headRadiusPixels;
|
}
|
||||||
graphics2D.drawOval(headCenterX - headRadiusPixels, headCenterY - headRadiusPixels,
|
if (stage >= 2) { // corps
|
||||||
headRadiusPixels * 2, headRadiusPixels * 2);
|
g2.drawLine(headCX, bodyTopY, headCX, bodyBottomY);
|
||||||
|
}
|
||||||
|
if (stage >= 3) { // bras gauche
|
||||||
|
g2.drawLine(headCX, shouldersY, headCX - armSpan, shouldersY + height / 20);
|
||||||
|
}
|
||||||
|
if (stage >= 4) { // bras droit
|
||||||
|
g2.drawLine(headCX, shouldersY, headCX + armSpan, shouldersY + height / 20);
|
||||||
|
}
|
||||||
|
if (stage >= 5) { // jambe gauche
|
||||||
|
g2.drawLine(headCX, bodyBottomY, headCX - legSpan, bodyBottomY + height / 8);
|
||||||
|
}
|
||||||
|
if (stage >= 6) { // jambe droite
|
||||||
|
g2.drawLine(headCX, bodyBottomY, headCX + legSpan, bodyBottomY + height / 8);
|
||||||
|
}
|
||||||
|
|
||||||
// Corps
|
g2.dispose();
|
||||||
int bodyTopYCoordinate = headCenterY + headRadiusPixels;
|
|
||||||
int bodyBottomYCoordinate = bodyTopYCoordinate + height / 6;
|
|
||||||
graphics2D.drawLine(headCenterX, bodyTopYCoordinate, headCenterX, bodyBottomYCoordinate);
|
|
||||||
|
|
||||||
// Bras
|
|
||||||
int armSpanPixels = width / 10;
|
|
||||||
int shouldersYCoordinate = bodyTopYCoordinate + height / 24;
|
|
||||||
graphics2D.drawLine(headCenterX, shouldersYCoordinate,
|
|
||||||
headCenterX - armSpanPixels, shouldersYCoordinate + height / 20);
|
|
||||||
graphics2D.drawLine(headCenterX, shouldersYCoordinate,
|
|
||||||
headCenterX + armSpanPixels, shouldersYCoordinate + height / 20);
|
|
||||||
|
|
||||||
// Jambes
|
|
||||||
int legSpanPixels = width / 12;
|
|
||||||
graphics2D.drawLine(headCenterX, bodyBottomYCoordinate,
|
|
||||||
headCenterX - legSpanPixels, bodyBottomYCoordinate + height / 8);
|
|
||||||
graphics2D.drawLine(headCenterX, bodyBottomYCoordinate,
|
|
||||||
headCenterX + legSpanPixels, bodyBottomYCoordinate + height / 8);
|
|
||||||
|
|
||||||
graphics2D.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Affichage
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() { return ""; }
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/Event.java
Normal file
62
src/Event.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>Event</code> regroupe et branche tous les listeners liés à Fenetre.
|
||||||
|
* - Validation de la saisie (1 lettre A-Z, majuscule)
|
||||||
|
* - Action sur Entrée ou clic bouton
|
||||||
|
* - Notification du handler externe (fourni au constructeur)
|
||||||
|
* Aucune logique de jeu ici.
|
||||||
|
* @version 1.3
|
||||||
|
* author Adrien
|
||||||
|
* Date : 08-10-2025
|
||||||
|
*/
|
||||||
|
public class Event implements ActionListener {
|
||||||
|
|
||||||
|
private final Fenetre window;
|
||||||
|
private final Consumer<Character> onLetterSubmitted;
|
||||||
|
|
||||||
|
/** Constructeur : conserve les références et branche les événements. */
|
||||||
|
public Event(Fenetre window, Consumer<Character> onLetterSubmitted) {
|
||||||
|
this.window = window;
|
||||||
|
this.onLetterSubmitted = onLetterSubmitted;
|
||||||
|
wireEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branche les listeners sur les composants de Fenetre. */
|
||||||
|
private void wireEvents() {
|
||||||
|
JTextField letterInput = window.getLetterInput();
|
||||||
|
JButton sendButton = window.getSendButton();
|
||||||
|
|
||||||
|
// Même listener (this) pour Entrée et clic bouton
|
||||||
|
sendButton.addActionListener(this);
|
||||||
|
letterInput.addActionListener(this);
|
||||||
|
|
||||||
|
// UX : limiter à une seule lettre et forcer la majuscule (classe dédiée)
|
||||||
|
letterInput.addKeyListener(new LetterInputFilter(letterInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Réagit à Entrée ou au clic bouton : récupère, valide et transmet la lettre. */
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
|
JTextField letterInput = window.getLetterInput();
|
||||||
|
|
||||||
|
String inputText = letterInput.getText().trim().toUpperCase();
|
||||||
|
letterInput.setText(""); // reset du champ après tentative
|
||||||
|
|
||||||
|
// Validation : exactement une lettre A–Z
|
||||||
|
if (inputText.length() != 1 || !inputText.matches("[A-Z]")) {
|
||||||
|
JOptionPane.showMessageDialog(window.getWindow(), "Veuillez entrer une seule lettre (A-Z).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification du handler externe, sinon placeholder
|
||||||
|
if (onLetterSubmitted != null) {
|
||||||
|
onLetterSubmitted.accept(inputText.charAt(0));
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog(window.getWindow(),
|
||||||
|
"Lettre soumise (placeholder) : " + inputText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/Fenetre.java
152
src/Fenetre.java
@@ -1,41 +1,51 @@
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>Fenetre</code> gère uniquement l'interface graphique :
|
* La classe <code>Fenetre</code> gère uniquement l'interface graphique :
|
||||||
* - zone de dessin (remplaçable par la classe Dessin)
|
* - zone de dessin (assurée par la classe Dessin)
|
||||||
* - affichage du mot masqué
|
* - affichage du mot masqué
|
||||||
* - saisie d'une lettre (événement remonté via un handler)
|
* - saisie d'une lettre (les événements sont gérés dans Event)
|
||||||
*
|
*
|
||||||
* Aucune logique de jeu (choix/vérification du mot) n'est ici.
|
* Aucune logique de jeu ici.
|
||||||
*
|
* @version 1.5
|
||||||
* * @version 0.3
|
|
||||||
* @author Adrien
|
* @author Adrien
|
||||||
* Date : 08-10-25
|
* Date : 08-10-2025
|
||||||
* Licence :
|
* Licence :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class Fenetre {
|
public class Fenetre {
|
||||||
|
|
||||||
// --- Composants de l'interface (UI only) ---
|
// --- Composants de l'interface ---
|
||||||
private JFrame window;
|
private JFrame window;
|
||||||
private JLabel wordLabel;
|
private JLabel wordLabel;
|
||||||
private JTextField letterInput;
|
private JTextField letterInput;
|
||||||
private JButton sendButton;
|
private JButton sendButton;
|
||||||
private JPanel drawZone; // remplaçable par la classe Dessin
|
private JPanel drawZone;
|
||||||
|
private Chronometre chronometre;
|
||||||
|
private JLabel scoreLabel;
|
||||||
|
|
||||||
// Handler externe pour la lettre soumise (fourni par la classe Partie / contrôleur)
|
// --- Constructeur ---
|
||||||
private Consumer<Character> onLetterSubmitted;
|
|
||||||
|
|
||||||
// --- Constructeur (courte orchestration uniquement) ---
|
|
||||||
public Fenetre() {
|
public Fenetre() {
|
||||||
setupWindow();
|
setupWindow();
|
||||||
JPanel root = createRoot();
|
|
||||||
|
JPanel root = new JPanel();
|
||||||
|
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
|
||||||
window.setContentPane(root);
|
window.setContentPane(root);
|
||||||
|
|
||||||
drawZone = createDrawZone();
|
// Barre supérieure : chrono + score
|
||||||
|
JPanel topBar = new JPanel(new BorderLayout());
|
||||||
|
chronometre = new Chronometre();
|
||||||
|
topBar.add(chronometre, BorderLayout.CENTER);
|
||||||
|
scoreLabel = new JLabel("Score : " + Score.BASE, SwingConstants.RIGHT);
|
||||||
|
scoreLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 12));
|
||||||
|
scoreLabel.setFont(new Font("Segoe UI", Font.BOLD, 16));
|
||||||
|
topBar.add(scoreLabel, BorderLayout.EAST);
|
||||||
|
root.add(topBar);
|
||||||
|
chronometre.start(); // démarrage automatique
|
||||||
|
|
||||||
|
drawZone = new Dessin();
|
||||||
root.add(drawZone);
|
root.add(drawZone);
|
||||||
|
|
||||||
wordLabel = createWordLabel();
|
wordLabel = createWordLabel();
|
||||||
@@ -49,7 +59,6 @@ public class Fenetre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Initialisation fenêtre ---
|
// --- Initialisation fenêtre ---
|
||||||
/** Configure la fenêtre principale (titre, taille, fermeture). */
|
|
||||||
private void setupWindow() {
|
private void setupWindow() {
|
||||||
window = new JFrame("Jeu du Pendu");
|
window = new JFrame("Jeu du Pendu");
|
||||||
window.setSize(600, 600);
|
window.setSize(600, 600);
|
||||||
@@ -57,23 +66,7 @@ public class Fenetre {
|
|||||||
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Crée le conteneur principal vertical. */
|
|
||||||
private JPanel createRoot() {
|
|
||||||
JPanel root = new JPanel();
|
|
||||||
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Sous-composants UI ---
|
// --- Sous-composants UI ---
|
||||||
/** Crée la zone de dessin placeholder (à remplacer par Dessin). */
|
|
||||||
private JPanel createDrawZone() {
|
|
||||||
JPanel panel = new PlaceholderDrawPanel();
|
|
||||||
panel.setPreferredSize(new Dimension(600, 350));
|
|
||||||
panel.setBackground(new Color(245, 245, 245));
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Crée le label d'affichage du mot (placeholder jusqu'à injection par Partie). */
|
|
||||||
private JLabel createWordLabel() {
|
private JLabel createWordLabel() {
|
||||||
JLabel label = new JLabel("_ _ _ _ _", SwingConstants.CENTER);
|
JLabel label = new JLabel("_ _ _ _ _", SwingConstants.CENTER);
|
||||||
label.setFont(new Font("Segoe UI", Font.BOLD, 32));
|
label.setFont(new Font("Segoe UI", Font.BOLD, 32));
|
||||||
@@ -82,7 +75,6 @@ public class Fenetre {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Crée la zone de saisie (champ + bouton) et branche l'action. */
|
|
||||||
private JPanel createInputPanel() {
|
private JPanel createInputPanel() {
|
||||||
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
|
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
|
||||||
JLabel prompt = new JLabel("Entrez une lettre :");
|
JLabel prompt = new JLabel("Entrez une lettre :");
|
||||||
@@ -93,92 +85,18 @@ public class Fenetre {
|
|||||||
|
|
||||||
sendButton = new JButton("Envoyer");
|
sendButton = new JButton("Envoyer");
|
||||||
|
|
||||||
Action sendAction = new AbstractAction() {
|
|
||||||
@Override public void actionPerformed(ActionEvent e) { submitLetter(); }
|
|
||||||
};
|
|
||||||
sendButton.addActionListener(sendAction);
|
|
||||||
letterInput.addActionListener(sendAction);
|
|
||||||
|
|
||||||
letterInput.addKeyListener(new KeyAdapter() {
|
|
||||||
@Override public void keyTyped(KeyEvent e) {
|
|
||||||
char c = e.getKeyChar();
|
|
||||||
if (!Character.isLetter(c) || letterInput.getText().length() >= 1) e.consume();
|
|
||||||
else e.setKeyChar(Character.toUpperCase(c));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
inputPanel.add(prompt);
|
inputPanel.add(prompt);
|
||||||
inputPanel.add(letterInput);
|
inputPanel.add(letterInput);
|
||||||
inputPanel.add(sendButton);
|
inputPanel.add(sendButton);
|
||||||
return inputPanel;
|
return inputPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Partie et Dessin ---
|
// --- Getters pour Event ---
|
||||||
|
public JFrame getWindow() { return window; }
|
||||||
/** Définit le texte du mot affiché (ex: "_ _ A _ _") fourni par la classe Partie. */
|
public JTextField getLetterInput() { return letterInput; }
|
||||||
public void setWordDisplay(String maskedWord) {
|
public JButton getSendButton() { return sendButton; }
|
||||||
wordLabel.setText(maskedWord != null ? maskedWord : "");
|
public JLabel getWordLabel() { return wordLabel; }
|
||||||
}
|
public JPanel getDrawZone() { return drawZone; }
|
||||||
|
public Chronometre getChronometre() { return chronometre; }
|
||||||
/** Définit le panneau de dessin (remplace le placeholder par la classe Dessin). */
|
public JLabel getScoreLabel() { return scoreLabel; }
|
||||||
public void setDrawPanel(JPanel dessinPanel) {
|
}
|
||||||
if (dessinPanel == null) return;
|
|
||||||
Container parent = drawZone.getParent();
|
|
||||||
int index = -1;
|
|
||||||
for (int i = 0; i < parent.getComponentCount(); i++) {
|
|
||||||
if (parent.getComponent(i) == drawZone) { index = i; break; }
|
|
||||||
}
|
|
||||||
if (index != -1) {
|
|
||||||
parent.remove(index);
|
|
||||||
drawZone = dessinPanel;
|
|
||||||
drawZone.setPreferredSize(new Dimension(600, 350));
|
|
||||||
parent.add(drawZone, index);
|
|
||||||
parent.revalidate();
|
|
||||||
parent.repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Définit le handler appelé quand l'utilisateur soumet une lettre. */
|
|
||||||
public void setOnLetterSubmitted(Consumer<Character> handler) {
|
|
||||||
this.onLetterSubmitted = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Méthodes privées UI ---
|
|
||||||
|
|
||||||
/** Récupère la lettre et notifie le handler externe (aucune logique de jeu ici). */
|
|
||||||
private void submitLetter() {
|
|
||||||
String text = letterInput.getText().trim().toUpperCase();
|
|
||||||
letterInput.setText("");
|
|
||||||
if (text.length() != 1 || !text.matches("[A-Z]")) {
|
|
||||||
JOptionPane.showMessageDialog(window, "Veuillez entrer une seule lettre (A-Z).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (onLetterSubmitted != null) onLetterSubmitted.accept(text.charAt(0));
|
|
||||||
else JOptionPane.showMessageDialog(window, "Lettre soumise (placeholder) : " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Placeholder de dessin (pour tester la fenêtre seule) ---
|
|
||||||
private static class PlaceholderDrawPanel extends JPanel {
|
|
||||||
@Override protected void paintComponent(Graphics g) {
|
|
||||||
super.paintComponent(g);
|
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
|
||||||
g2.setStroke(new BasicStroke(2f));
|
|
||||||
g2.drawRect(10, 10, getWidth() - 20, getHeight() - 20);
|
|
||||||
g2.setFont(getFont().deriveFont(Font.BOLD, 16f));
|
|
||||||
String txt = "Zone de dessin (remplacer par Dessin)";
|
|
||||||
FontMetrics fm = g2.getFontMetrics();
|
|
||||||
int x = (getWidth() - fm.stringWidth(txt)) / 2;
|
|
||||||
int y = (getHeight() + fm.getAscent()) / 2;
|
|
||||||
g2.drawString(txt, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Méthode principale de test : exécuter Fenetre seule (sans Partie/Dessin) ---
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Fenetre f = new Fenetre();
|
|
||||||
f.setWordDisplay("_ _ _ _ _"); // placeholder
|
|
||||||
f.setDrawPanel(new Dessin()); // dessin
|
|
||||||
f.setOnLetterSubmitted(ch -> JOptionPane.showMessageDialog(null,"Lettre reçue : " + ch + " (sans logique de jeu)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
47
src/LetterInputFilter.java
Normal file
47
src/LetterInputFilter.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>LetterInputFilter</code> limite la saisie d'un champ texte
|
||||||
|
* à une seule lettre de l'alphabet (A–Z), en forçant automatiquement
|
||||||
|
* la majuscule. Toute autre frappe est ignorée.
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @author Adrien
|
||||||
|
* Date : 08-10-2025
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public class LetterInputFilter extends KeyAdapter {
|
||||||
|
|
||||||
|
/** Référence vers le champ de saisie à filtrer. */
|
||||||
|
private final JTextField letterInput;
|
||||||
|
|
||||||
|
//Constructeur.
|
||||||
|
public LetterInputFilter(JTextField letterInput) {
|
||||||
|
if (letterInput == null) {
|
||||||
|
throw new NullPointerException("letterInput ne doit pas être null");
|
||||||
|
}
|
||||||
|
this.letterInput = letterInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepte les frappes clavier et applique les règles suivantes :
|
||||||
|
* - n'accepte que les lettres (A–Z)
|
||||||
|
* - limite la saisie à un seul caractère
|
||||||
|
* - force la majuscule sur le caractère saisi.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void keyTyped(KeyEvent keyEvent) {
|
||||||
|
char typedChar = keyEvent.getKeyChar();
|
||||||
|
|
||||||
|
// Refuse tout caractère non alphabétique ou une saisie > 1 caractère.
|
||||||
|
if (!Character.isLetter(typedChar) || letterInput.getText().length() >= 1) {
|
||||||
|
keyEvent.consume(); // ignore la frappe
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force la majuscule.
|
||||||
|
keyEvent.setKeyChar(Character.toUpperCase(typedChar));
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/MenuDifficulte.java
Normal file
75
src/MenuDifficulte.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fenêtre de sélection de la difficulté (Facile / Moyen / Difficile).
|
||||||
|
* Notifie le choix via un Consumer<String> fourni au constructeur.
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @author Adrien
|
||||||
|
* Date : 08-10-2025
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public class MenuDifficulte implements ActionListener {
|
||||||
|
|
||||||
|
private final JFrame frame;
|
||||||
|
private final Consumer<String> onDifficultyChosen;
|
||||||
|
|
||||||
|
/** Construit la fenêtre et prépare les boutons. */
|
||||||
|
public MenuDifficulte(Consumer<String> onDifficultyChosen) {
|
||||||
|
this.onDifficultyChosen = onDifficultyChosen;
|
||||||
|
|
||||||
|
frame = new JFrame("Jeu du Pendu — Choix de difficulté");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
frame.setSize(400, 250);
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setLayout(new BorderLayout(0, 10));
|
||||||
|
|
||||||
|
JLabel title = new JLabel("Choisissez une difficulté", SwingConstants.CENTER);
|
||||||
|
title.setFont(new Font("Segoe UI", Font.BOLD, 18));
|
||||||
|
title.setBorder(BorderFactory.createEmptyBorder(20, 10, 0, 10));
|
||||||
|
frame.add(title, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(new GridLayout(1, 3, 10, 10));
|
||||||
|
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||||
|
|
||||||
|
JButton easyBtn = new JButton("Facile");
|
||||||
|
JButton mediumBtn = new JButton("Moyen");
|
||||||
|
JButton hardBtn = new JButton("Difficile");
|
||||||
|
|
||||||
|
easyBtn.setActionCommand("Facile");
|
||||||
|
mediumBtn.setActionCommand("Moyen");
|
||||||
|
hardBtn.setActionCommand("Difficile");
|
||||||
|
|
||||||
|
easyBtn.addActionListener(this);
|
||||||
|
mediumBtn.addActionListener(this);
|
||||||
|
hardBtn.addActionListener(this);
|
||||||
|
|
||||||
|
buttonsPanel.add(easyBtn);
|
||||||
|
buttonsPanel.add(mediumBtn);
|
||||||
|
buttonsPanel.add(hardBtn);
|
||||||
|
frame.add(buttonsPanel, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Affiche la fenêtre. */
|
||||||
|
public void show() { frame.setVisible(true); }
|
||||||
|
|
||||||
|
/** Ferme la fenêtre. */
|
||||||
|
public void close() { frame.dispose(); }
|
||||||
|
|
||||||
|
/** Accès optionnel au JFrame. */
|
||||||
|
public JFrame getFrame() { return frame; }
|
||||||
|
|
||||||
|
/** Réception des clics sur les boutons. */
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
|
String difficulty = actionEvent.getActionCommand(); // "Facile" | "Moyen" | "Difficile"
|
||||||
|
frame.dispose();
|
||||||
|
if (onDifficultyChosen != null) {
|
||||||
|
onDifficultyChosen.accept(difficulty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/Mots.java
Normal file
58
src/Mots.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>Mots</code>
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @author Aurélien
|
||||||
|
* Date : 08-10-25
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public final class Mots {
|
||||||
|
//Attributs
|
||||||
|
public static final short dictionarysize = 32 ;
|
||||||
|
public static final String[] dictionary = {
|
||||||
|
"Magnifique",
|
||||||
|
"Etoile",
|
||||||
|
"Voyage",
|
||||||
|
"Biscuit",
|
||||||
|
"Refrigerateur",
|
||||||
|
"Courage",
|
||||||
|
"Avion",
|
||||||
|
"Explorateur",
|
||||||
|
"Montagne",
|
||||||
|
"Philosophie",
|
||||||
|
"Lumiere",
|
||||||
|
"Ethernet",
|
||||||
|
"Architecture",
|
||||||
|
"Ocean",
|
||||||
|
"Liberte",
|
||||||
|
"Aventure",
|
||||||
|
"Cerise",
|
||||||
|
"Harmonieux",
|
||||||
|
"Informatique",
|
||||||
|
"Pluie",
|
||||||
|
"Equilibriste",
|
||||||
|
"Papillon",
|
||||||
|
"Saisons",
|
||||||
|
"Liberte",
|
||||||
|
"Alphabet",
|
||||||
|
"Musique",
|
||||||
|
"Translucent",
|
||||||
|
"Passion",
|
||||||
|
"Etreindre",
|
||||||
|
"Poetique",
|
||||||
|
"Serenite",
|
||||||
|
"Révolution"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
private Mots() { //N'a pas pour but d'être instanciée
|
||||||
|
throw new UnsupportedOperationException("The \"Fichier\" class cannot be instanced !");
|
||||||
|
}
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
//Affichage
|
||||||
|
public String toString() {
|
||||||
|
return "" ;
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/Partie.java
121
src/Partie.java
@@ -1,23 +1,136 @@
|
|||||||
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>Partie</code>
|
* La classe <code>Partie</code>
|
||||||
*
|
*
|
||||||
* @version
|
* @version 0.2
|
||||||
* @author
|
* @author Aurélien
|
||||||
* Date :
|
* Date : 08-10-25
|
||||||
* Licence :
|
* Licence :
|
||||||
*/
|
*/
|
||||||
public class Partie {
|
public class Partie {
|
||||||
|
//Contantes
|
||||||
|
private static final byte REMAININGTRY = 6 ;
|
||||||
|
private static final byte CARACTERCODESHIFT = 65 ; //Décalage ASCI > 'A'
|
||||||
|
|
||||||
//Attributs
|
//Attributs
|
||||||
|
private char[] secretword ;
|
||||||
|
private byte wordsize ;
|
||||||
|
private boolean[] foundletters ;
|
||||||
|
private boolean[] entriesletters = new boolean[26] ; //Pseudo Alphabée
|
||||||
|
private byte remainingtry = REMAININGTRY ;
|
||||||
|
|
||||||
//Constructeur
|
//Constructeur
|
||||||
public Partie() {
|
public Partie() {
|
||||||
|
this.secretword = generateSecretWord() ;
|
||||||
|
this.wordsize = (byte) secretword.length ;
|
||||||
|
this.foundletters = new boolean[wordsize] ;
|
||||||
}
|
}
|
||||||
//Méthodes
|
//Méthodes
|
||||||
|
public char[] getSecretWord() {
|
||||||
|
return this.secretword ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean[] getFoundLetters() {
|
||||||
|
return this.foundletters ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getRemainingTry() {
|
||||||
|
return this.remainingtry ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie l'état de la partie en cours.
|
||||||
|
*
|
||||||
|
* @return true si le jeu est fini.
|
||||||
|
*/
|
||||||
|
public boolean gameIsEnding() {
|
||||||
|
if(this.remainingtry <= 0){
|
||||||
|
return true ;
|
||||||
|
}else if(wordIsFound()){
|
||||||
|
return true ;
|
||||||
|
}else{
|
||||||
|
return false ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la lettre reçu n'a pas déjà été joué puis, met à jour le tableau "entriesletters" et
|
||||||
|
* "foundletters" le cas échéant.
|
||||||
|
*
|
||||||
|
* @return true si la lettre était déjà présente.
|
||||||
|
*/
|
||||||
|
public boolean isAlreadyEntries(char letter) {
|
||||||
|
short caractercode = (short) letter ; //Récupération du code du caractère
|
||||||
|
if(this.entriesletters[caractercode-CARACTERCODESHIFT]){
|
||||||
|
this.remainingtry-- ; //Décrément des essais
|
||||||
|
return true ;
|
||||||
|
}else{
|
||||||
|
boolean isfind = false ;
|
||||||
|
for(byte i = 0 ; i < this.wordsize ; i++){ //Parcours du "secretword"
|
||||||
|
if(this.secretword[i] == letter){
|
||||||
|
this.foundletters[i] = true ;
|
||||||
|
isfind = true ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isfind == false){
|
||||||
|
this.remainingtry-- ; //Décrément des essais
|
||||||
|
}
|
||||||
|
this.entriesletters[caractercode-CARACTERCODESHIFT] = true ; //Ajout au tableau des lettres jouées
|
||||||
|
return false ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un mot à partir d'un grand dictionnaire (enfin en principe).
|
||||||
|
*
|
||||||
|
* @return le mot généré.
|
||||||
|
*/
|
||||||
|
private char[] generateSecretWord() {
|
||||||
|
Random random = new Random();
|
||||||
|
byte grain = (byte) random.nextInt(Mots.dictionarysize);
|
||||||
|
char[] word = Mots.dictionary[grain].toUpperCase().toCharArray();
|
||||||
|
return word ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean wordIsFound() {
|
||||||
|
for(byte i = 0 ; i < this.wordsize ; i++){ //Parcours du "secretword"
|
||||||
|
if(!this.foundletters[i]){ //Si une lettre n'est pas trouvé
|
||||||
|
return false ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Affichage
|
//Affichage
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "" ;
|
return "" ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
public static void main(String[] args){
|
||||||
|
char[] test = {'E','O','M','I','E','D','A','Z','N','L','C','R','P','H','T','S'};
|
||||||
|
byte size = (byte) test.length ;
|
||||||
|
boolean status ;
|
||||||
|
|
||||||
|
Partie game = new Partie();
|
||||||
|
System.out.println("Trick > " + String.valueOf(game.secretword) + "\n");
|
||||||
|
for(byte i = 0 ; i < size && !game.gameIsEnding() ; i++){
|
||||||
|
System.out.println("Essais restants : " + game.getRemainingTry());
|
||||||
|
status = game.isAlreadyEntries(test[i]);
|
||||||
|
for(byte l = 0 ; l < game.wordsize ; l++){ //Parcours du "secretword"
|
||||||
|
if(game.foundletters[l] == true){
|
||||||
|
System.out.print(game.getSecretWord()[l] + " ");
|
||||||
|
}else{
|
||||||
|
System.out.print("_ ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println(""); //Lisibilité
|
||||||
|
//System.out.println("Lettres : " + game.entriesletters);
|
||||||
|
}
|
||||||
|
System.out.println("Essais restants : " + game.getRemainingTry());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
139
src/Pendu.java
139
src/Pendu.java
@@ -1,14 +1,135 @@
|
|||||||
|
import javax.swing.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>Pendu</code>
|
* Point d'entrée : affiche le menu de difficulté puis lance la fenêtre du jeu.
|
||||||
*
|
* Lie Fenetre (vue) et Partie (logique) via un handler.
|
||||||
* @version
|
* Met à jour le dessin du pendu à chaque erreur.
|
||||||
* @author
|
*
|
||||||
* Date :
|
* @version 1.4
|
||||||
* Licence :
|
* author Adrien
|
||||||
*/
|
* Date : 08-10-2025
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
public class Pendu {
|
public class Pendu {
|
||||||
public static void main(String[] args){
|
|
||||||
|
|
||||||
}
|
public static void main(String[] args) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
MenuDifficulte menu = new MenuDifficulte(difficulty -> openGameWindow(difficulty));
|
||||||
|
menu.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ouvre la fenêtre du jeu, crée la Partie et branche les événements. */
|
||||||
|
private static void openGameWindow(String difficulty) {
|
||||||
|
Fenetre fenetre = new Fenetre();
|
||||||
|
fenetre.getWindow().setTitle("Jeu du Pendu — " + difficulty);
|
||||||
|
|
||||||
|
Partie partie = new Partie();
|
||||||
|
|
||||||
|
// Affichage initial du mot masqué (construit ici)
|
||||||
|
fenetre.getWordLabel().setText(buildMaskedWord(partie));
|
||||||
|
|
||||||
|
// Stage initial (0 erreur)
|
||||||
|
if (fenetre.getDrawZone() instanceof Dessin) {
|
||||||
|
((Dessin) fenetre.getDrawZone()).setStage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On mémorise les essais initiaux pour calculer les erreurs = initialTries - remainingTry
|
||||||
|
final int initialTries = partie.getRemainingTry();
|
||||||
|
|
||||||
|
// Handler : applique Partie puis met à jour l'UI (mot + dessin + score)
|
||||||
|
Consumer<Character> handler = new GameLetterHandler(fenetre, partie, initialTries);
|
||||||
|
|
||||||
|
// Branchement des événements clavier/bouton
|
||||||
|
new Event(fenetre, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Construit la chaîne "_ _ A _ _" à partir de l'état de Partie (sans modifier Partie). */
|
||||||
|
private static String buildMaskedWord(Partie partie) {
|
||||||
|
char[] word = partie.getSecretWord();
|
||||||
|
boolean[] found = partie.getFoundLetters();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < word.length; i++) {
|
||||||
|
sb.append(found[i] ? word[i] : '_');
|
||||||
|
if (i < word.length - 1) sb.append(' ');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler de lettres :
|
||||||
|
* - applique Partie.isAlreadyEntries
|
||||||
|
* - met à jour le mot affiché
|
||||||
|
* - calcule errors = initialTries - remainingTry, puis stage = min(errors, Dessin.MAX_STAGE)
|
||||||
|
* - met à jour le score
|
||||||
|
* - gère la fin de partie
|
||||||
|
*/
|
||||||
|
private static class GameLetterHandler implements Consumer<Character> {
|
||||||
|
private final Fenetre fenetre;
|
||||||
|
private final Partie partie;
|
||||||
|
private final int initialTries; // essais au démarrage (peut être 11 avec ta Partie)
|
||||||
|
private final Dessin dessinPanel;
|
||||||
|
|
||||||
|
GameLetterHandler(Fenetre fenetre, Partie partie, int initialTries) {
|
||||||
|
this.fenetre = fenetre;
|
||||||
|
this.partie = partie;
|
||||||
|
this.initialTries = initialTries;
|
||||||
|
this.dessinPanel = (fenetre.getDrawZone() instanceof Dessin)
|
||||||
|
? (Dessin) fenetre.getDrawZone() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Character ch) {
|
||||||
|
boolean alreadyPlayed = partie.isAlreadyEntries(ch);
|
||||||
|
|
||||||
|
// Mise à jour du mot
|
||||||
|
fenetre.getWordLabel().setText(buildMaskedWord(partie));
|
||||||
|
|
||||||
|
// Erreurs -> stage pour le dessin (borné à MAX_STAGE)
|
||||||
|
int errors = Math.max(0, initialTries - partie.getRemainingTry());
|
||||||
|
if (dessinPanel != null) {
|
||||||
|
int stage = Math.min(errors, Dessin.MAX_STAGE);
|
||||||
|
dessinPanel.setStage(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour du score courant (si tu utilises Score + Chronometre)
|
||||||
|
long elapsed = (fenetre.getChronometre() != null)
|
||||||
|
? fenetre.getChronometre().getElapsedMillis() : 0L;
|
||||||
|
if (fenetre.getScoreLabel() != null) {
|
||||||
|
int score = Score.compute(errors, elapsed);
|
||||||
|
fenetre.getScoreLabel().setText("Score : " + score);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alreadyPlayed) {
|
||||||
|
JOptionPane.showMessageDialog(fenetre.getWindow(),
|
||||||
|
"Lettre déjà jouée : " + ch + "\nEssais restants : " + partie.getRemainingTry());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fin de partie
|
||||||
|
if (partie.gameIsEnding()) {
|
||||||
|
// Stoppe le chronomètre pour figer le temps
|
||||||
|
if (fenetre.getChronometre() != null) {
|
||||||
|
fenetre.getChronometre().stop();
|
||||||
|
}
|
||||||
|
// Score final (si Score/Chronometre présents)
|
||||||
|
long finalElapsed = (fenetre.getChronometre() != null)
|
||||||
|
? fenetre.getChronometre().getElapsedMillis() : elapsed;
|
||||||
|
int finalErrors = Math.max(0, initialTries - partie.getRemainingTry());
|
||||||
|
int finalScore = Score.compute(finalErrors, finalElapsed);
|
||||||
|
|
||||||
|
boolean win = !fenetre.getWordLabel().getText().contains("_");
|
||||||
|
String msg = (win
|
||||||
|
? "Bravo ! Mot trouvé : "
|
||||||
|
: "Perdu ! Le mot était : ")
|
||||||
|
+ String.valueOf(partie.getSecretWord())
|
||||||
|
+ (fenetre.getScoreLabel() != null ? "\nScore : " + finalScore : "");
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(fenetre.getWindow(), msg);
|
||||||
|
|
||||||
|
fenetre.getLetterInput().setEnabled(false);
|
||||||
|
fenetre.getSendButton().setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Score.java
Normal file
36
src/Score.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Calcule le score du pendu à partir du nombre d'erreurs et du temps écoulé.
|
||||||
|
*
|
||||||
|
* Formule (simple et configurable) :
|
||||||
|
* score = max(0, BASE - erreurs * ERROR_PENALTY - secondes * TIME_PENALTY_PER_SEC)
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @author Adrien
|
||||||
|
* Date : 08-10-2025
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public final class Score {
|
||||||
|
|
||||||
|
/** Score de départ. */
|
||||||
|
public static final int BASE = 1000;
|
||||||
|
|
||||||
|
/** Malus par erreur. (6 erreurs max par défaut) */
|
||||||
|
public static final int ERROR_PENALTY = 120;
|
||||||
|
|
||||||
|
/** Malus par seconde écoulée. */
|
||||||
|
public static final double TIME_PENALTY_PER_SEC = 1.0;
|
||||||
|
|
||||||
|
private Score() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - nombre d'erreurs (>=0)
|
||||||
|
* - temps écoulé en millisecondes
|
||||||
|
* - score >= 0
|
||||||
|
*/
|
||||||
|
public static int compute(int errors, long elapsedMillis) {
|
||||||
|
if (errors < 0) errors = 0;
|
||||||
|
double timePenalty = (elapsedMillis / 1000.0) * TIME_PENALTY_PER_SEC;
|
||||||
|
int value = (int)Math.round(BASE - errors * ERROR_PENALTY - timePenalty);
|
||||||
|
return Math.max(0, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user