diff --git a/src/Chronometre.java b/src/Chronometre.java
new file mode 100644
index 0000000..609f020
--- /dev/null
+++ b/src/Chronometre.java
@@ -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));
+ }
+}
diff --git a/src/Dessin.java b/src/Dessin.java
index c90cfaf..27a8f12 100644
--- a/src/Dessin.java
+++ b/src/Dessin.java
@@ -2,87 +2,101 @@ import javax.swing.*;
import java.awt.*;
/**
-* La classe Dessin gère uniquement le dessin du pendu
+* La classe Dessin gère uniquement le dessin du pendu,
+* avec révélation progressive en fonction du nombre d'erreurs (stage).
*
-* @version 1.0
-* @author Adrien
+* @version 1.1
+* author Adrien
* Date : 08-10-2025
-* Licence :
*/
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() {
- // Taille préférée pour s'intégrer dans Fenetre
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);
- // Anti-aliasing pour des traits plus doux
- Graphics2D graphics2D = (Graphics2D) graphics.create();
- graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- graphics2D.setStroke(new BasicStroke(3f));
- graphics2D.setColor(Color.DARK_GRAY);
+ 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 marginPixels = Math.min(width, height) / 12; // marge proportionnelle
+ int margin = Math.min(width, height) / 12;
- // Potence : socle
- int baseYCoordinate = height - marginPixels;
- graphics2D.drawLine(marginPixels, baseYCoordinate, width / 2, baseYCoordinate);
-
- // Montant vertical
- int postXCoordinate = marginPixels + (width / 12);
- graphics2D.drawLine(postXCoordinate, baseYCoordinate, postXCoordinate, marginPixels);
-
- // Traverse horizontale
+ // 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;
- 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
- graphics2D.drawLine(postXCoordinate, marginPixels + height / 10, postXCoordinate + width / 12, marginPixels);
+ // Corde (toujours)
+ int ropeX = postX + beamLength;
+ int ropeTopY = margin;
+ int ropeBottomY = margin + height / 12;
+ g2.drawLine(ropeX, ropeTopY, ropeX, ropeBottomY);
- // Corde
- int ropeXCoordinate = postXCoordinate + beamLength;
- int ropeTopYCoordinate = marginPixels;
- int ropeBottomYCoordinate = marginPixels + height / 12;
- graphics2D.drawLine(ropeXCoordinate, ropeTopYCoordinate, ropeXCoordinate, ropeBottomYCoordinate);
+ // 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;
- // Personnage : tête
- int headRadiusPixels = Math.min(width, height) / 16;
- int headCenterX = ropeXCoordinate;
- int headCenterY = ropeBottomYCoordinate + headRadiusPixels;
- graphics2D.drawOval(headCenterX - headRadiusPixels, headCenterY - headRadiusPixels, headRadiusPixels * 2, headRadiusPixels * 2);
+ // É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);
+ }
- // Corps
- 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();
+ g2.dispose();
}
- // Affichage
@Override
- public String toString() {
- return "";
- }
+ public String toString() { return ""; }
}
diff --git a/src/Fenetre.java b/src/Fenetre.java
index a62c14e..dac077f 100644
--- a/src/Fenetre.java
+++ b/src/Fenetre.java
@@ -22,7 +22,9 @@ public class Fenetre {
private JLabel wordLabel;
private JTextField letterInput;
private JButton sendButton;
- private JPanel drawZone; // instance de Dessin
+ private JPanel drawZone;
+ private Chronometre chronometre;
+ private JLabel scoreLabel;
// --- Constructeur ---
public Fenetre() {
@@ -32,6 +34,17 @@ public class Fenetre {
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);
@@ -84,15 +97,6 @@ public class Fenetre {
public JButton getSendButton() { return sendButton; }
public JLabel getWordLabel() { return wordLabel; }
public JPanel getDrawZone() { return drawZone; }
-
- // --- Méthode principale de test ---
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- Fenetre f = new Fenetre();
- // On passe le handler directement ici (pas de setOnLetterSubmitted)
- new Event(f, ch ->
- JOptionPane.showMessageDialog(f.getWindow(), "Lettre reçue : " + ch + " (sans logique de jeu)")
- );
- });
- }
+ public Chronometre getChronometre() { return chronometre; }
+ public JLabel getScoreLabel() { return scoreLabel; }
}
\ No newline at end of file
diff --git a/src/MenuDifficulte.java b/src/MenuDifficulte.java
new file mode 100644
index 0000000..81af18c
--- /dev/null
+++ b/src/MenuDifficulte.java
@@ -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 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 onDifficultyChosen;
+
+ /** Construit la fenêtre et prépare les boutons. */
+ public MenuDifficulte(Consumer 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);
+ }
+ }
+}
diff --git a/src/Partie.java b/src/Partie.java
index c96cdf5..05dbde9 100644
--- a/src/Partie.java
+++ b/src/Partie.java
@@ -1,15 +1,14 @@
-
/**
* La classe Partie
*
-* @version 0.1
+* @version 0.2
* @author Aurélien
* Date : 08-10-25
* Licence :
*/
public class Partie {
//Contantes
- private static final byte REMAININGTRY = 11 ;
+ private static final byte REMAININGTRY = 6 ;
private static final byte CARACTERCODESHIFT = 65 ; //Décalage ASCI > 'A'
//Attributs
@@ -25,25 +24,23 @@ public class Partie {
this.wordsize = (byte) secretword.length ;
this.foundletters = new boolean[wordsize] ;
}
- //Méthodes
- public char[] getSecretWord() {
- return this.secretword ;
+
+ // Getters
+ public char[] getSecretWord() { return this.secretword ; }
+ public boolean[] getFoundLetters() { return this.foundletters ; }
+ public byte getRemainingTry() { return this.remainingtry ; }
+
+ /** Représentation masquée du mot, ex: "_ _ A _ _" */
+ public String getMaskedWord() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < secretword.length; i++) {
+ sb.append(foundletters[i] ? secretword[i] : '_');
+ if (i < secretword.length - 1) sb.append(' ');
+ }
+ return sb.toString();
}
- 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.
- */
+ /** Vérifie l'état de la partie en cours. */
public boolean gameIsEnding() {
if(this.remainingtry <= 0){
return true ;
@@ -55,10 +52,9 @@ public class Partie {
}
/**
- * 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.
+ * Vérifie si la lettre reçue a déjà été jouée puis met à jour
+ * entriesletters / foundletters / remainingtry.
+ * @return true si la lettre était déjà présente (doublon).
*/
public boolean isAlreadyEntries(char letter) {
short caractercode = (short) letter ; //Récupération du code du caractère
@@ -81,48 +77,21 @@ public class Partie {
}
}
-
private char[] generateSecretWord() {
- char[] word = {'D','A','M','I','E','N'};
+ char[] word = {'P','I','Z','Z','A'};
//À implémenter plus tard
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é
+ for(byte i = 0 ; i < this.wordsize ; i++){
+ if(!this.foundletters[i]){ //Si une lettre n'est pas trouvée
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'};
- 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);
- }
- }
+ @Override
+ public String toString() { return "" ; }
}
diff --git a/src/Pendu.java b/src/Pendu.java
index e478d9c..dcdcddc 100644
--- a/src/Pendu.java
+++ b/src/Pendu.java
@@ -1,14 +1,117 @@
+import javax.swing.*;
+import java.util.function.Consumer;
/**
-* La classe Pendu
-*
-* @version
-* @author
-* Date :
-* Licence :
-*/
+ * 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.3
+ * author Adrien
+ * Date : 08-10-2025
+ * Licence :
+ */
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é (via Partie)
+ fenetre.getWordLabel().setText(partie.getMaskedWord());
+
+ // Stage initial (0 erreur)
+ if (fenetre.getDrawZone() instanceof Dessin) {
+ ((Dessin) fenetre.getDrawZone()).setStage(0);
+ }
+
+ // Handler : applique Partie puis met à jour l'UI (mot + dessin)
+ Consumer handler = new GameLetterHandler(fenetre, partie);
+
+ // Branchement des événements clavier/bouton
+ new Event(fenetre, handler);
+ }
+
+ /**
+ * Handler de lettres :
+ * - applique Partie.isAlreadyEntries
+ * - met à jour le mot affiché (Partie#getMaskedWord)
+ * - calcule le stage = MAX_STAGE - remainingTry et met à jour le Dessin
+ * - gère la fin de partie
+ */
+ private static class GameLetterHandler implements Consumer {
+ private final Fenetre fenetre;
+ private final Partie partie;
+ private final Dessin dessinPanel;
+
+ GameLetterHandler(Fenetre fenetre, Partie partie) {
+ this.fenetre = fenetre;
+ this.partie = partie;
+ 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(partie.getMaskedWord());
+
+ // Erreurs -> stage pour le dessin
+ if (dessinPanel != null) {
+ int stage = Dessin.MAX_STAGE - partie.getRemainingTry();
+ dessinPanel.setStage(stage);
+ }
+
+ // Mise à jour du score courant (erreurs + temps actuel)
+ long elapsed = (fenetre.getChronometre() != null)
+ ? fenetre.getChronometre().getElapsedMillis() : 0L;
+ int errors = Dessin.MAX_STAGE - partie.getRemainingTry();
+ int score = Score.compute(errors, elapsed);
+ if (fenetre.getScoreLabel() != null) {
+ 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 recalculé (au cas où une seconde vient de passer)
+ long finalElapsed = (fenetre.getChronometre() != null)
+ ? fenetre.getChronometre().getElapsedMillis() : elapsed;
+ int finalErrors = Dessin.MAX_STAGE - 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())
+ + "\nScore : " + finalScore;
+
+ JOptionPane.showMessageDialog(fenetre.getWindow(), msg);
+
+ fenetre.getLetterInput().setEnabled(false);
+ fenetre.getSendButton().setEnabled(false);
+ }
+ }
+ }
}
diff --git a/src/Score.java b/src/Score.java
new file mode 100644
index 0000000..2bed1b3
--- /dev/null
+++ b/src/Score.java
@@ -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);
+ }
+}