diff --git a/res/hangman_words.txt b/res/hangman_words.txt new file mode 100644 index 0000000..2da5687 --- /dev/null +++ b/res/hangman_words.txt @@ -0,0 +1,554 @@ +#EASY +banane +ceinture +maman +jupe +canard +grand +miroir +oiseau +neuf +dos +lune +fruit +lampe +pain +balle +blanc +enfant +mouton +gris +ane +pomme +tete +pigeon +maison +citrouille +arbre +poule +rat +ventre +jour +coq +renard +vache +sel +neige +bureau +poisson +bois +lunette +ami +petit +bouche +court +peigne +nuage +barbe +arbre +sucre +plage +fenetre +os +oreille +feuille +jour +poire +cle +lait +livre +eau +chat +herbe +fraise +route +main +air +tomate +aigle +yeux +raisin +seconde +bus +feu +tante +peau +cheveu +rouge +eau +porte +lapin +cle +jardin +oncle +pied +main +cerise +pied +paon +minute +pluie +robe +champ +ciel +oiseau +nuit +porte +frere +heure +vert +papa +couleur +coeur +noir +mur +mur +chemise +pingouin +lit +doigt +table +carotte +grenouille +orage +bleu +chapeau +bras +gant +plage +voisin +corbeau +mer +amie +chien +fromage +nez +chaud +zebre +femme +train +canari +velo +patate +orange +chaton +sol +serviette +fruit +pantalon +chiot +photo +etoile +souris +salon +fleur +poule +jambe +horloge +nez +manteau +verre +vieux +canard +homme +colombe +chat +montagne +poireau +cheval +ville +vallee +veste +pluie +fleur +neige +jaune +soeur +viande +feuille +oeuf +cravate +soleil +citron +lac +moto +abricot +singe +vent +tigre +rivière +papier +ecole +long +neige +brosse +cochon +rue +terre +riz +pont +porte +vent +toit +rocher +sac +chaussure +robe +salade +dent +crayon +lion +montre +foret +bateau +cuisine +voiture +chien +nuit +poisson +lune +lac +soleil +ours +savon +langue +chaise +froid +melon +clef +terre +voisine +chapeau +#MEDIUM +serpent +orage +bracelet +savonnette +broche +parapluie +valise +helicoptere +histoire +coureur +brosse +galaxie +mouchoir +ete +veste +montagne +train +bijou +logiciel +dessin +fichier +fantome +baleine +nuage +foret +station +saladier +montagne +costume +minuteur +chateau +casque +clavier +menuisier +manteau +lezard +vallee +tableau +mystere +chapeau +shampoing +chaussure +port +aeroport +printemps +dauphin +ordinateur +theatre +fourchette +pneu +dentiste +lunette +acteur +salle +serveur +aventure +requin +gel +bague +moteur +rivière +bouteille +uniforme +couloir +batterie +remorque +bureau +station +tempete +arcenciel +tasse +docteur +tortue +vendeur +peintre +verglas +semaine +cravate +telephone +tracteur +montre +miroir +plume +magasin +musicien +plage +pantalon +essence +pingouin +cinema +camion +cascade +coussin +boucle +animal +cuisine +avion +parebrise +mois +acheteur +cabane +jardinier +bouteille +brouillard +porte +saison +toilette +voilier +hiver +bouteille +avion +horloge +glacier +musique +tortue +conducteur +fusée +carrosserie +ceinture +echelle +rideau +chambre +phare +peinture +journee +cactus +baleine +chaussure +voiture +frein +journal +internet +minute +caravane +collier +couteau +automne +lecteur +embrayage +danseur +piano +camionette +serpent +gare +eclair +souris +facteur +armoire +pendentif +boulanger +fenetre +ecran +serviette +bateau +rivière +verre +chanteur +lampe +miroir +assiette +tapis +auteur +couscous +neige +chemise +professeur +volant +concert +autruche +dossier +policier +perroquet +biscuit +seconde +plombier +tonnerre +salon +siege +garage +poesie +#HARD +adaptation +paradoxe +imaginatif +evaluation +constructif +parabole +contemplation +constellation +geometrie +atomique +transformation +hypotenuse +oxymore +metaphore +mathematique +cryptographie +anthropologie +combustion +stratosphere +chlorophylle +hemisphere +archipel +encyclopedie +biologique +chrysantheme +linguistique +cartographie +thermodynamique +rectangle +destructif +algorithmique +hydraulique +dichotomie +multiplication +conservation +organisation +hippopotame +moleculaire +manipulation +photosynthese +coordination +constitution +consideration +photosensible +radioactif +sphynx +restauration +rhythmique +bibliotheque +administration +neurologie +orthographique +parallaxe +acoustique +informatique +phonetique +conique +univers +entomologie +observation +phonetique +planetaire +cardiologue +onomatopee +labyrinthe +syntaxique +nucleaire +instinctif +representation +contradiction +isoceles +galaxie +antinomie +mythologie +decomposition +polygonal +ornithologie +cylindre +quantique +spherique +physique +subjectif +telepathie +distribution +grammaticale +asteroide +aristocratie +isotherme +comete +subatomique +archaeologie +optique +quadrilatere +technocratie +lexicale +linguistique +equilateral +psychologie +metamorphose +bacteriologie +collaboration +paraphrase +meteorologie +revolutionnaire +astronomie +hexagone +responsabilite +semantique +juridiction +gravitation +physiologie +cosmique +creatif +electrochoc +thermometre +orbite +conjoncture +inspiration +solaire +palindrome +parallelogramme +satellite +pseudonyme +lithographie +introspectif +xylophone +substantif +parlementaire +pragmatique +approximation +objectif +genetique +electronique +productif +interpretation +philosophie +chimique +caracteristique +communication +abstraction +ellipse +totalitaire +independance +pyramide +trapèze +democratie +prisme +extrapolation +economique +nebuleuse +anachronisme \ No newline at end of file diff --git a/src/HangmanGUI.java b/src/HangmanGUI.java new file mode 100644 index 0000000..23b721d --- /dev/null +++ b/src/HangmanGUI.java @@ -0,0 +1,320 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.Timer; +/** + * Hangman GUI with difficulty selection, dynamic score, and timer. + * Uses WordManager for words. + * User-facing texts remain in French. + */ +public class HangmanGUI extends JFrame { + private WordManager manager; + private String secretWord; + private final Set found = new HashSet<>(); + private final Set tried = new HashSet<>(); + private int errors = 0; + private static final int MAX_ERRORS = 8; + private static final int WORD_GUESS_PENALTY = 2; // pénalité (en erreurs) si le mot complet proposé est faux + private boolean gameOver = false; + + // scoring and timing + private int baseScore = 200; + private int mistakePenalty = 15; + private double timePenaltyPerSecond = 1.0; + private long startTime; + + // UI components + private final JLabel wordLbl = new JLabel("", SwingConstants.CENTER); + private final JLabel triedLbl = new JLabel("", SwingConstants.CENTER); + private final JLabel infoLbl = new JLabel("Entrez une lettre ou un mot (a-z)", SwingConstants.CENTER); + private final JLabel scoreLbl = new JLabel("Score: 0", SwingConstants.CENTER); + private final JLabel timerLbl = new JLabel("Temps: 0s", SwingConstants.CENTER); + private final JTextField input = new JTextField(); + private final JButton guessBtn = new JButton("Proposer"); + private final HangPanel hangPanel = new HangPanel(); + private final String[] difficulties = {"Facile", "Moyen", "Difficile"}; + private final JComboBox difficultyCombo = new JComboBox<>(difficulties); + private Timer timer; + + /** Drawing panel for hangman */ + private static class HangPanel extends JPanel { + private int errors = 0; + public void setErrors(int e) { + this.errors = Math.max(0, Math.min(e, MAX_ERRORS)); + repaint(); + } + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D gg = (Graphics2D) g; + gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + int w = getWidth(), h = getHeight(); + int baseY = h - 40; + + if (errors >= 1) gg.drawLine(50, baseY, w - 50, baseY); + if (errors >= 2) gg.drawLine(100, baseY, 100, 60); + if (errors >= 3) gg.drawLine(100, 60, 220, 60); + if (errors >= 4) gg.drawLine(220, 60, 220, 90); + if (errors >= 5) gg.drawOval(200, 90, 40, 40); + if (errors >= 6) gg.drawLine(220, 130, 220, 200); + if (errors >= 7) { + gg.drawLine(220, 145, 190, 165); + gg.drawLine(220, 145, 250, 165); + } + if (errors >= 8) { + gg.drawLine(220, 200, 200, 240); + gg.drawLine(220, 200, 240, 240); + } + } + } + + /** Entry point */ + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + HangmanGUI app = new HangmanGUI(); + app.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** Constructor */ + public HangmanGUI() throws Exception { + super("Jeu du Pendu"); + manager = new WordManager(); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setSize(560, 620); + setLocationRelativeTo(null); + setupUI(); + startGame(); + } + + /** Setup UI */ + private void setupUI() { + wordLbl.setFont(new Font(Font.MONOSPACED, Font.BOLD, 28)); + triedLbl.setFont(new Font("SansSerif", Font.PLAIN, 14)); + infoLbl.setFont(new Font("SansSerif", Font.PLAIN, 14)); + scoreLbl.setFont(new Font("SansSerif", Font.BOLD, 16)); + timerLbl.setFont(new Font("SansSerif", Font.BOLD, 16)); + + JPanel top = new JPanel(new GridLayout(6, 1, 8, 8)); + top.add(wordLbl); + top.add(triedLbl); + top.add(infoLbl); + + JPanel scorePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 5)); + scorePanel.add(scoreLbl); + scorePanel.add(timerLbl); + top.add(scorePanel); + + JPanel diffPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + diffPanel.add(new JLabel("Difficulté:")); + diffPanel.add(difficultyCombo); + top.add(diffPanel); + + JButton restartBtn = new JButton("Rejouer"); + restartBtn.addActionListener(e -> startGame()); + JPanel restartPanel = new JPanel(); + restartPanel.add(restartBtn); + top.add(restartPanel); + + JPanel controls = new JPanel(new BorderLayout(8, 8)); + controls.add(input, BorderLayout.CENTER); + controls.add(guessBtn, BorderLayout.EAST); + + hangPanel.setPreferredSize(new Dimension(500, 300)); + JPanel center = new JPanel(new BorderLayout()); + center.add(hangPanel, BorderLayout.CENTER); + + JPanel main = new JPanel(new BorderLayout(10, 10)); + main.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + main.add(top, BorderLayout.NORTH); + main.add(center, BorderLayout.CENTER); + main.add(controls, BorderLayout.SOUTH); + setContentPane(main); + + guessBtn.addActionListener(e -> onGuess()); + input.addActionListener(e -> onGuess()); + addWindowListener(new WindowAdapter() { + @Override public void windowOpened(WindowEvent e) { input.requestFocusInWindow(); } + }); + } + + /** Start new game */ + private void startGame() { + int diffIndex = difficultyCombo.getSelectedIndex(); // 0=Facile,1=Moyen,2=Difficile + int diffValue = diffIndex + 1; + secretWord = manager.getRandomWord(diffValue).toLowerCase(Locale.ROOT); + + found.clear(); + tried.clear(); + errors = 0; + gameOver = false; + + startTime = System.currentTimeMillis(); + input.setText(""); + input.setEditable(true); + guessBtn.setEnabled(true); + infoLbl.setText("Entrez une lettre ou un mot (a-z)"); + + updateUIState(); + + if (timer != null) timer.cancel(); + timer = new Timer(true); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + SwingUtilities.invokeLater(() -> { + updateScoreLabel(); + updateTimerLabel(); + }); + } + }, 0, 200); + } + + /** Handle guess (lettre OU mot) */ + private void onGuess() { + if (gameOver) return; + + String s = input.getText().trim().toLowerCase(Locale.ROOT); + + // autoriser uniquement les lettres a-z, mais longueur libre (1 = lettre, >1 = mot) + if (!s.matches("[a-z]+")) { + infoLbl.setText("Veuillez entrer uniquement des lettres (a-z)."); + input.selectAll(); + input.requestFocusInWindow(); + return; + } + + if (s.length() == 1) { + // === Proposition d'une lettre === + char c = s.charAt(0); + if (tried.contains(c)) { + infoLbl.setText("Lettre déjà essayée : " + c); + input.selectAll(); + input.requestFocusInWindow(); + return; + } + tried.add(c); + + boolean hit = false; + for (int i = 0; i < secretWord.length(); i++) { + if (secretWord.charAt(i) == c) { + found.add(c); + hit = true; + } + } + if (!hit) { + errors++; + infoLbl.setText("Mauvaise lettre : " + c); + } else { + infoLbl.setText("Bonne lettre : " + c); + } + } else { + // === Proposition d'un mot entier === + if (s.equals(secretWord)) { + // victoire immédiate : révéler tout + for (int i = 0; i < secretWord.length(); i++) { + char lc = secretWord.charAt(i); + if (Character.isLetter(lc)) found.add(lc); + } + infoLbl.setText("Bravo ! Vous avez deviné le mot en entier : " + secretWord); + } else { + // pénalité si mot faux + errors = Math.min(errors + WORD_GUESS_PENALTY, MAX_ERRORS); + infoLbl.setText("Mauvaise proposition du mot."); + } + } + + updateUIState(); + input.selectAll(); + input.requestFocusInWindow(); + } + + /** Update UI */ + private void updateUIState() { + wordLbl.setText(maskWord(secretWord, found)); + triedLbl.setText("Lettres essayées : " + joinChars(tried)); + hangPanel.setErrors(errors); + updateScoreLabel(); + updateTimerLabel(); + + if (isWin(secretWord, found)) { + infoLbl.setText("Bravo ! Vous avez trouvé le mot : " + secretWord); + wordLbl.setText(spaced(secretWord)); + gameOver = true; + disableInput(); + } else if (errors >= MAX_ERRORS) { + infoLbl.setText("Perdu ! Le mot était : " + secretWord); + wordLbl.setText(spaced(secretWord)); + gameOver = true; + disableInput(); + } + } + + /** Update dynamic score */ + private void updateScoreLabel() { + long elapsedMillis = System.currentTimeMillis() - startTime; + double elapsedSeconds = elapsedMillis / 1000.0; + int score = (int) (baseScore - errors * mistakePenalty - elapsedSeconds * timePenaltyPerSecond); + scoreLbl.setText("Score: " + Math.max(score, 0)); + } + + /** Update dynamic timer */ + private void updateTimerLabel() { + long elapsedMillis = System.currentTimeMillis() - startTime; + int seconds = (int) (elapsedMillis / 1000); + timerLbl.setText("Temps: " + seconds + "s"); + } + + /** Disable input after end */ + private void disableInput() { + input.setEditable(false); + guessBtn.setEnabled(false); + if (timer != null) timer.cancel(); + } + + private String maskWord(String secret, Set foundSet) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < secret.length(); i++) { + char ch = secret.charAt(i); + char lc = Character.toLowerCase(ch); + if (Character.isLetter(lc) && !foundSet.contains(lc)) sb.append('_'); + else sb.append(ch); + if (i < secret.length() - 1) sb.append(' '); + } + return sb.toString(); + } + + private boolean isWin(String secret, Set foundSet) { + for (int i = 0; i < secret.length(); i++) { + char lc = Character.toLowerCase(secret.charAt(i)); + if (Character.isLetter(lc) && !foundSet.contains(lc)) return false; + } + return true; + } + + private String joinChars(Set set) { + java.util.List list = new ArrayList<>(set); + Collections.sort(list); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + sb.append(list.get(i)); + if (i < list.size() - 1) sb.append(' '); + } + return sb.toString(); + } + + private String spaced(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + sb.append(s.charAt(i)); + if (i < s.length() - 1) sb.append(' '); + } + return sb.toString(); + } +} diff --git a/src/WordManager.java b/src/WordManager.java new file mode 100644 index 0000000..a19c831 --- /dev/null +++ b/src/WordManager.java @@ -0,0 +1,55 @@ +import java.io.*; +import java.util.*; + +public class WordManager { + // --- Constant: path to word file --- + private static final String WORD_FILE = "../res/hangman_words.txt"; + + // --- Map to store words by difficulty --- + private final Map> wordsByDifficulty = new HashMap<>(); + + // --- Constructor --- + public WordManager() throws IOException { + wordsByDifficulty.put("EASY", new ArrayList<>()); + wordsByDifficulty.put("MEDIUM", new ArrayList<>()); + wordsByDifficulty.put("HARD", new ArrayList<>()); + loadWords(); // automatically load words when created + } + + // --- Load words from the file --- + private void loadWords() throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader(WORD_FILE))) { + String line; + String section = ""; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("#")) { + section = line.substring(1).toUpperCase(); // read difficulty section + } else if (!line.isEmpty() && wordsByDifficulty.containsKey(section)) { + wordsByDifficulty.get(section).add(line); + } + } + } catch (FileNotFoundException e) { + throw new IOException("File not found: " + WORD_FILE); + } + } + + // --- Get a random word based on difficulty (1 = Easy, 2 = Medium, 3 = Hard) --- + public String getRandomWord(int difficulty) { + String key = difficultyToKey(difficulty); + List list = wordsByDifficulty.get(key); + if (list == null || list.isEmpty()) return null; + Random rand = new Random(); + return list.get(rand.nextInt(list.size())); + } + + // --- Convert int to difficulty key --- + private String difficultyToKey(int difficulty) { + return switch (difficulty) { + case 1 -> "EASY"; + case 2 -> "MEDIUM"; + case 3 -> "HARD"; + default -> "EASY"; // default difficulty + }; + } +}