29 Commits

Author SHA1 Message Date
5e70bcbb63 Merge pull request 'final' (#9) from dick into master
Reviewed-on: dick/TD3_DEV51_dick_amary#9
2025-10-08 17:57:21 +02:00
e649d30b33 Final 2025-10-08 17:56:14 +02:00
584e8dc3c6 Final 2025-10-08 17:55:54 +02:00
82b4e0ffd1 Merge issue 2025-10-08 17:32:51 +02:00
9cb9088b81 Merge pull request 'amary' (#8) from amary into master
Reviewed-on: dick/TD3_DEV51_dick_amary#8
2025-10-08 17:21:07 +02:00
77b3158262 Petit dictionnaire 2025-10-08 17:19:31 +02:00
0f7c1f6b71 Petit dictionnaire 2025-10-08 17:18:54 +02:00
87909bbe35 Logique de base + chrono + score 2025-10-08 17:07:29 +02:00
9d7c681ca3 Merge pull request 'amary' (#6) from amary into master
Reviewed-on: dick/TD3_DEV51_dick_amary#6
2025-10-08 16:15:54 +02:00
e6e0d4cf71 Correctif noms 2025-10-08 16:14:39 +02:00
ffbe1e232e Retirer Console 2025-10-08 16:09:04 +02:00
a3627d9023 Merge pull request 'Fix classe anonyme' (#5) from dick into master
Reviewed-on: dick/TD3_DEV51_dick_amary#5
2025-10-08 16:07:36 +02:00
2a8eac0720 Gestion de partie (sans génération) 2025-10-08 16:07:03 +02:00
7206dca3ef Fix classe anonyme 2025-10-08 15:33:32 +02:00
0fa7ec527f Merge pull request 'Fenetre et Dessin v1' (#4) from dick into master
Reviewed-on: dick/TD3_DEV51_dick_amary#4
2025-10-08 15:17:54 +02:00
f7aecb038d Event commentaire 2025-10-08 15:16:13 +02:00
62f6f94be7 Event commentaire 2025-10-08 15:15:04 +02:00
90874a3296 Event fix 2025-10-08 15:04:08 +02:00
49e32b316e Fenetre fix + Event.java 2025-10-08 14:53:45 +02:00
06daa4e478 Fenetre fix dessin temp 2025-10-08 14:08:31 +02:00
35c9fcd873 Fenetre fix dessin temp 2025-10-08 14:08:10 +02:00
ba7e16dc64 Fenetre et Dessin v1 2025-10-08 12:13:42 +02:00
721543075c Merge pull request 'Super Makefile' (#3) from amary into master
Reviewed-on: dick/TD3_DEV51_dick_amary#3
2025-10-08 11:04:20 +02:00
d7ace2c8be Super Makefile 2025-10-08 11:01:36 +02:00
56009640f5 Merge pull request 'Readme' (#2) from dick into master
Reviewed-on: dick/TD3_DEV51_dick_amary#2
2025-10-08 10:42:21 +02:00
80264f552f Merge branch 'master' into dick 2025-10-08 10:39:58 +02:00
258aab00f3 Merge pull request 'Base' (#1) from amary into master
Reviewed-on: dick/TD3_DEV51_dick_amary#1
2025-10-08 10:34:58 +02:00
5e8e4a1183 Readme 2025-10-08 10:29:37 +02:00
1741908a9d Base 2025-10-08 10:26:19 +02:00
17 changed files with 983 additions and 0 deletions

1
Diagramme.mdj Normal file

File diff suppressed because one or more lines are too long

72
Makefile Normal file
View File

@@ -0,0 +1,72 @@
# Projet Pendu : fichier Makefile
# Compatibilité : Linux
# Règle par défaut
all : Pendu
# Dossiers
IN = src/
OUT = bin/
# Mots-clés
JC = javac
JCFLAGS = -encoding UTF-8 -implicit:none -cp $(OUT) -d $(OUT)
CLASSFILES = Pendu.class \
Partie.class \
Fenetre.class \
Dessin.class \
Mots.class \
Event.class \
LetterInputFilter.class \
MenuDifficulte.class \
Chronometre.class \
Score.class
# Dépendances
$(OUT)Pendu.class : $(IN)Pendu.java $(OUT)Partie.class $(OUT)Fenetre.class $(OUT)Event.class $(OUT)MenuDifficulte.class $(OUT)Score.class
$(JC) $(JCFLAGS) $<
$(OUT)Partie.class : $(IN)Partie.java $(OUT)Mots.class
$(JC) $(JCFLAGS) $<
$(OUT)Fenetre.class : $(IN)Fenetre.java $(OUT)Partie.class $(OUT)Dessin.class $(OUT)Chronometre.class $(OUT)Score.class
$(JC) $(JCFLAGS) $<
$(OUT)Dessin.class : $(IN)Dessin.java
$(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
Pendu : $(OUT)Pendu.class
jar : $(OUT)Pendu.class
jar -cfe Pendu.jar Pendu -C $(OUT) .
clean :
-rm -f $(OUT)*.class
-rm -f Pendu.jar
help : #(à implémenter plus tard)
# Buts factices
.PHONY : all clean #(pour les cibles qui sont des commandes)
# Bug : gestion des chemins dans jar ?

View File

@@ -0,0 +1,23 @@
/**
* La classe <code>Class</code>
*
* @version
* @author
* Date :
* Licence :
*/
public class Class {
//Attributs
//Constructeur
public Class() {
}
//Méthodes
//Affichage
public String toString() {
return "" ;
}
}

View File

@@ -0,0 +1,23 @@
import javax.swing.*;
import java.awt.event.*;
/**
* La classe <code>Event</code>
*
* @version
* @author
* Date :
* Licence :
*/
public class Event implements ActionListener {
// Attributs
// Constructeur de l'évennement
public Event() {
}
// Action de l'évennement
public void actionPerformed(ActionEvent event){
}
}

View File

@@ -0,0 +1,14 @@
/**
* L'interface <code>Interface</code>
*
* @version
* @author
* Date :
* Licence :
*/
public interface Interface {
//Méthodes
public void Action() ;
}

14
Modèles/Modèle Main.txt Normal file
View File

@@ -0,0 +1,14 @@
/**
* La classe <code>Main</code>
*
* @version
* @author
* Date :
* Licence :
*/
public class Main {
public static void main(String[] args){
}
}

0
README.md Normal file
View File

83
src/Chronometre.java Normal file
View 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));
}
}

102
src/Dessin.java Normal file
View File

@@ -0,0 +1,102 @@
import javax.swing.*;
import java.awt.*;
/**
* 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 1.1
* author Adrien
* Date : 08-10-2025
*/
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 ---
public Dessin() {
setPreferredSize(new Dimension(600, 350));
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 ---
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g2 = (Graphics2D) graphics.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3f));
g2.setColor(Color.DARK_GRAY);
// Repères et proportions
int width = getWidth();
int height = getHeight();
int margin = Math.min(width, height) / 12;
// Potence (toujours affichée)
int baseY = height - margin;
g2.drawLine(margin, baseY, width / 2, baseY); // socle
int postX = margin + (width / 12);
g2.drawLine(postX, baseY, postX, margin); // montant
int beamLength = width / 3;
g2.drawLine(postX, margin, postX + beamLength, margin); // traverse
g2.drawLine(postX, margin + height / 10, postX + width / 12, margin); // renfort
// Corde (toujours)
int ropeX = postX + beamLength;
int ropeTopY = margin;
int ropeBottomY = margin + height / 12;
g2.drawLine(ropeX, ropeTopY, ropeX, ropeBottomY);
// Géométrie du personnage
int headR = Math.min(width, height) / 16;
int headCX = ropeX;
int headCY = ropeBottomY + headR;
int bodyTopY = headCY + headR;
int bodyBottomY = bodyTopY + height / 6;
int armSpan = width / 10;
int shouldersY = bodyTopY + height / 24;
int legSpan = width / 12;
// Étapes du personnage
if (stage >= 1) { // tête
g2.drawOval(headCX - headR, headCY - headR, headR * 2, headR * 2);
}
if (stage >= 2) { // corps
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);
}
g2.dispose();
}
@Override
public String toString() { return ""; }
}

62
src/Event.java Normal file
View 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 AZ
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);
}
}
}

102
src/Fenetre.java Normal file
View File

@@ -0,0 +1,102 @@
import javax.swing.*;
import java.awt.*;
import java.util.function.Consumer;
/**
* La classe <code>Fenetre</code> gère uniquement l'interface graphique :
* - zone de dessin (assurée par la classe Dessin)
* - affichage du mot masqué
* - saisie d'une lettre (les événements sont gérés dans Event)
*
* Aucune logique de jeu ici.
* @version 1.5
* @author Adrien
* Date : 08-10-2025
* Licence :
*/
public class Fenetre {
// --- Composants de l'interface ---
private JFrame window;
private JLabel wordLabel;
private JTextField letterInput;
private JButton sendButton;
private JPanel drawZone;
private Chronometre chronometre;
private JLabel scoreLabel;
// --- Constructeur ---
public Fenetre() {
setupWindow();
JPanel root = new JPanel();
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
window.setContentPane(root);
// 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);
wordLabel = createWordLabel();
root.add(wordLabel);
JPanel inputPanel = createInputPanel();
root.add(inputPanel);
window.setVisible(true);
letterInput.requestFocusInWindow();
}
// --- Initialisation fenêtre ---
private void setupWindow() {
window = new JFrame("Jeu du Pendu");
window.setSize(600, 600);
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// --- Sous-composants UI ---
private JLabel createWordLabel() {
JLabel label = new JLabel("_ _ _ _ _", SwingConstants.CENTER);
label.setFont(new Font("Segoe UI", Font.BOLD, 32));
label.setBorder(BorderFactory.createEmptyBorder(20, 10, 20, 10));
label.setAlignmentX(Component.CENTER_ALIGNMENT);
return label;
}
private JPanel createInputPanel() {
JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
JLabel prompt = new JLabel("Entrez une lettre :");
letterInput = new JTextField(2);
letterInput.setHorizontalAlignment(JTextField.CENTER);
letterInput.setFont(new Font("Segoe UI", Font.BOLD, 24));
sendButton = new JButton("Envoyer");
inputPanel.add(prompt);
inputPanel.add(letterInput);
inputPanel.add(sendButton);
return inputPanel;
}
// --- Getters pour Event ---
public JFrame getWindow() { return window; }
public JTextField getLetterInput() { return letterInput; }
public JButton getSendButton() { return sendButton; }
public JLabel getWordLabel() { return wordLabel; }
public JPanel getDrawZone() { return drawZone; }
public Chronometre getChronometre() { return chronometre; }
public JLabel getScoreLabel() { return scoreLabel; }
}

View 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 (AZ), 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 (AZ)
* - 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
View 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
View 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 "" ;
}
}

136
src/Partie.java Normal file
View File

@@ -0,0 +1,136 @@
import java.util.Random;
/**
* La classe <code>Partie</code>
*
* @version 0.2
* @author Aurélien
* Date : 08-10-25
* Licence :
*/
public class Partie {
//Contantes
private static final byte REMAININGTRY = 6 ;
private static final byte CARACTERCODESHIFT = 65 ; //Décalage ASCI > 'A'
//Attributs
private char[] secretword ;
private byte wordsize ;
private boolean[] foundletters ;
private boolean[] entriesletters = new boolean[26] ; //Pseudo Alphabée
private byte remainingtry = REMAININGTRY ;
//Constructeur
public Partie() {
this.secretword = generateSecretWord() ;
this.wordsize = (byte) secretword.length ;
this.foundletters = new boolean[wordsize] ;
}
//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
public String toString() {
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());
}
}

135
src/Pendu.java Normal file
View File

@@ -0,0 +1,135 @@
import javax.swing.*;
import java.util.function.Consumer;
/**
* 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.
* Met à jour le dessin du pendu à chaque erreur.
*
* @version 1.4
* author Adrien
* Date : 08-10-2025
* Licence :
*/
public class Pendu {
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
View 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);
}
}