diff --git a/Jeu.db b/Jeu.db
deleted file mode 100644
index ae846f7..0000000
Binary files a/Jeu.db and /dev/null differ
diff --git a/linea/BouleBonus.java b/linea/BouleBonus.java
index 7b95a0a..c5014ab 100644
--- a/linea/BouleBonus.java
+++ b/linea/BouleBonus.java
@@ -11,6 +11,14 @@ public class BouleBonus extends ObjetGraphique {
private double vitesse = 5.0;
private boolean estVerte; // true = bonus (niveau +1), false = malus (niveau -1)
private int frameCounter = 0;
+ private double vitesseVerticale = 0.0;
+ private double amplitudeOndulation = 0.2;
+ private double vitesseHorizontaleLisse = 0.0;
+ private double phaseOndulation = Math.random() * Math.PI * 2; // phase unique par boule
+
+ private static double clamp(double value, double min, double max) {
+ return Math.max(min, Math.min(max, value));
+ }
public BouleBonus(double x, double y, boolean estVerte) {
this.x = x;
@@ -19,8 +27,14 @@ public class BouleBonus extends ObjetGraphique {
if (estVerte) {
this.couleur = new Color(0.0f, 0.8f, 0.0f); // Vert
+ this.rayon = 15;
+ this.amplitudeOndulation = 0.45; // onde visible plus ample
+ this.vitesseHorizontaleLisse = 4.8;
} else {
this.couleur = new Color(0.8f, 0.0f, 0.0f); // Rouge
+ this.rayon = 14;
+ this.amplitudeOndulation = 0.14;
+ this.vitesseHorizontaleLisse = 4.3;
}
}
@@ -37,20 +51,18 @@ public class BouleBonus extends ObjetGraphique {
Graphics2D g2D = (Graphics2D) g;
// Effet de pulsation (le rayon augmente/diminue)
- double rayonAffiche = rayon + Math.sin(frameCounter * 0.1) * 3;
+ double rayonAffiche = rayon + Math.sin(frameCounter * 0.12) * 1.5;
- // Dessiner le cercle rempli
+ // Dessiner le cercle rempli (centré sur x,y)
g2D.setColor(couleur);
- g2D.fillOval((int)(x - rayonAffiche/2), (int)(y - rayonAffiche),
- (int)rayonAffiche, (int)rayonAffiche);
+ g2D.fillOval((int)(x - rayonAffiche), (int)(y - rayonAffiche),
+ (int)(2 * rayonAffiche), (int)(2 * rayonAffiche));
// Contour
g2D.setStroke(new BasicStroke(2.0f));
g2D.setColor(estVerte ? new Color(0.0f, 1.0f, 0.0f) : new Color(1.0f, 0.0f, 0.0f));
- g2D.drawOval((int)(x - rayonAffiche/2), (int)(y - rayonAffiche),
- (int)rayonAffiche, (int)rayonAffiche);
-
- frameCounter++;
+ g2D.drawOval((int)(x - rayonAffiche), (int)(y - rayonAffiche),
+ (int)(2 * rayonAffiche), (int)(2 * rayonAffiche));
}
@Override
@@ -58,6 +70,64 @@ public class BouleBonus extends ObjetGraphique {
// Déplacement vers la gauche (même vitesse que la ligne)
x -= vitesse;
}
+
+ public void animerAvecCible(double vitesseLigne, double cibleY) {
+ animerAvecCible(vitesseLigne, cibleY, 0.0);
+ }
+
+ public void animerAvecCible(double vitesseLigne, double cibleY, double cibleVitesseY) {
+ // Vertes: trajectoire fluide à intercepter. Rouges: poursuite plus nette avec fenêtre d'esquive.
+ double facteurHorizontal = estVerte ? 0.88 : 0.64;
+ double vitesseMax = estVerte ? 9.2 : 5.8;
+ double vitessePoursuite = estVerte ? 0.075 : 0.050; // meilleure réactivité
+ double limiteVerticale = estVerte ? 1.85 : 1.05; // accélération verticale
+ double amortissement = estVerte ? 0.87 : 0.81; // moins de friction
+ double zoneMorte = estVerte ? 18.0 : 16.0; // zone mort réduite
+ double vitesseVerticaleMax = estVerte ? 4.2 : 2.6; // vitesses verticales plus hautes
+
+ double vitesseLigneSecurisee = Math.max(0.0, vitesseLigne);
+ double vitesseCible = Math.min(vitesseMax, vitesseLigneSecurisee * facteurHorizontal);
+ if (!estVerte) {
+ // Les rouges gardent une vitesse plus stable d'une boule à l'autre.
+ vitesseCible = vitesseCible * 0.68 + 4.5 * 0.32;
+ }
+ double deltaVitesse = clamp(vitesseCible - vitesseHorizontaleLisse, -0.25, 0.25);
+ double vitesseMaxLocale = vitesseMax;
+ if (!estVerte && x < 380) {
+ // En fin d'écran, on plafonne un peu la vitesse pour laisser une fenêtre d'esquive stable.
+ double facteurApproche = clamp((380.0 - x) / 180.0, 0.0, 1.0);
+ vitesseMaxLocale = vitesseMax - (0.55 * facteurApproche);
+ }
+ vitesseHorizontaleLisse = clamp(vitesseHorizontaleLisse + deltaVitesse, 4.0, vitesseMaxLocale);
+ vitesse = vitesseHorizontaleLisse;
+ x -= vitesse;
+
+ double vitesseJoueurLimitee = clamp(cibleVitesseY, -6.0, 6.0);
+ // Verte: anticipation légère + oscillation. Rouge: AUCUNE anticipation → trajectoire lisible, esquivable.
+ double anticipation = estVerte ? vitesseJoueurLimitee * 3.2 : 0.0;
+ double cibleLisse = cibleY + anticipation + (estVerte ? Math.sin(frameCounter * 0.055 + phaseOndulation) * 45.0 : 0.0);
+
+ double deltaY = cibleLisse - y;
+ if (Math.abs(deltaY) < zoneMorte) {
+ deltaY = 0;
+ }
+
+ vitesseVerticale += clamp(deltaY * vitessePoursuite, -limiteVerticale, limiteVerticale);
+ vitesseVerticale *= amortissement;
+ vitesseVerticale = clamp(vitesseVerticale, -vitesseVerticaleMax, vitesseVerticaleMax);
+
+ double ondulation = Math.sin(frameCounter * 0.085 + (estVerte ? 0.0 : 1.1)) * amplitudeOndulation;
+ y += vitesseVerticale + ondulation;
+ frameCounter++;
+
+ if (y < 20) {
+ y = 20;
+ vitesseVerticale = 0;
+ } else if (y > 580) {
+ y = 580;
+ vitesseVerticale = 0;
+ }
+ }
// Vérifier collision avec le cercle
public boolean collisionAvec(Cercle c) {
@@ -66,7 +136,8 @@ public class BouleBonus extends ObjetGraphique {
double cRayon = c.getRayon();
double dist = Math.hypot(cx - x, cy - y);
- return dist <= (rayon + cRayon);
+ double seuil = estVerte ? (rayon + cRayon - 6.0) : (rayon + cRayon - 9.0);
+ return dist <= seuil;
}
public void setVitesse(double vitesse) {
diff --git a/linea/Cercle.java b/linea/Cercle.java
index c311c30..96564cb 100644
--- a/linea/Cercle.java
+++ b/linea/Cercle.java
@@ -71,6 +71,10 @@ public class Cercle extends ObjetGraphique{ // il s'agit plutôt d'arcs de cercl
//-------------------------------------------------------------------------
public void Monter(){
montee = true;
+ // Évite l'effet "chute incontrôlable" quand on reprend la montée tard.
+ if (vitesse > 2.0) {
+ vitesse = 2.0;
+ }
}
@@ -108,31 +112,36 @@ public class Cercle extends ObjetGraphique{ // il s'agit plutôt d'arcs de cercl
//-------------------------------------------------------------------------
@Override
void Animer() {
- // pas est à prendre comme un "delta t"
-
- // chute libre
- vitesse = vitesse + 9.81 * pas;
+ double gravite = 0.95;
+ double poussee = 1.45;
+ double amortissement = 0.92;
- // impulsion
if (montee==true) {
- vitesse = vitesse - impulsion *pas;
+ vitesse -= poussee;
+ } else {
+ vitesse += gravite;
}
-
- depY = 1/2 * 9.81 + vitesse * pas;
-
- if (depY<-10) {
- depY=-10;
+
+ // Lissage global pour un ressenti plus régulier.
+ vitesse *= amortissement;
+
+ if (vitesse < -6.5) {
+ vitesse = -6.5;
}
- if (depY>10){
- depY =10;
+ if (vitesse > 6.5) {
+ vitesse = 6.5;
}
- y+=depY;
+
+ depY = vitesse;
+ y += depY;
//position
if(y<= 0 + rayon){
y = 0 + rayon;
+ vitesse = 0;
}else if(y>=600 - rayon){
y = 600 - rayon;
+ vitesse = 0;
}
}
diff --git a/linea/DatabaseConnection.java b/linea/DatabaseConnection.java
index 9f3d922..c240af5 100644
--- a/linea/DatabaseConnection.java
+++ b/linea/DatabaseConnection.java
@@ -65,14 +65,6 @@ public class DatabaseConnection {
);
""";
- String createNiveau = """
- CREATE TABLE IF NOT EXISTS Niveau (
- id_niveau INTEGER PRIMARY KEY AUTOINCREMENT,
- nom TEXT,
- nb_Objet INTEGER NOT NULL
- );
- """;
-
String createScore = """
CREATE TABLE IF NOT EXISTS Score (
id_score INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -80,16 +72,23 @@ public class DatabaseConnection {
nb_mort INTEGER,
temps_jeu INTEGER,
id_compte INTEGER,
- id_niveau INTEGER,
- FOREIGN KEY(id_compte) REFERENCES Compte(id_compte),
- FOREIGN KEY(id_niveau) REFERENCES Niveau(id_niveau)
+ FOREIGN KEY(id_compte) REFERENCES Compte(id_compte)
+ );
+ """;
+
+ String createProgressionCampagne = """
+ CREATE TABLE IF NOT EXISTS ProgressionCampagne (
+ id_compte INTEGER PRIMARY KEY,
+ niveau_debloque_max INTEGER NOT NULL DEFAULT 1,
+ FOREIGN KEY(id_compte) REFERENCES Compte(id_compte)
);
""";
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(createCompte);
- stmt.executeUpdate(createNiveau);
stmt.executeUpdate(createScore);
+ stmt.executeUpdate("DROP TABLE IF EXISTS Niveau");
+ stmt.executeUpdate(createProgressionCampagne);
System.out.println("Tables créées / existantes OK");
} catch (SQLException e) {
System.err.println("Erreur création tables : " + e.getMessage());
@@ -131,7 +130,11 @@ public class DatabaseConnection {
ps.setString(1, pseudo.trim());
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
- if (rs.next()) return rs.getInt(1);
+ if (rs.next()) {
+ int idCompte = rs.getInt(1);
+ initialiserProgressionCampagne(idCompte);
+ return idCompte;
+ }
}
} catch (SQLException e) {
System.err.println("Erreur création compte : " + e.getMessage());
@@ -155,8 +158,11 @@ public class DatabaseConnection {
public void supprimerCompte(int idCompte) {
if (conn == null || idCompte <= 0) return;
- try (PreparedStatement ps1 = conn.prepareStatement("DELETE FROM Score WHERE id_compte = ?");
+ try (PreparedStatement ps0 = conn.prepareStatement("DELETE FROM ProgressionCampagne WHERE id_compte = ?");
+ PreparedStatement ps1 = conn.prepareStatement("DELETE FROM Score WHERE id_compte = ?");
PreparedStatement ps2 = conn.prepareStatement("DELETE FROM Compte WHERE id_compte = ?")) {
+ ps0.setInt(1, idCompte);
+ ps0.executeUpdate();
ps1.setInt(1, idCompte);
ps1.executeUpdate();
ps2.setInt(1, idCompte);
@@ -181,6 +187,53 @@ public class DatabaseConnection {
return pseudos;
}
+ private void initialiserProgressionCampagne(int idCompte) {
+ if (conn == null || idCompte <= 0) return;
+ String sql = "INSERT OR IGNORE INTO ProgressionCampagne (id_compte, niveau_debloque_max) VALUES (?, 1)";
+ try (PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, idCompte);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ System.err.println("Erreur init progression campagne : " + e.getMessage());
+ }
+ }
+
+ public int getNiveauDebloqueCampagne(int idCompte) {
+ if (conn == null || idCompte <= 0) return 1;
+
+ initialiserProgressionCampagne(idCompte);
+ String sql = "SELECT niveau_debloque_max FROM ProgressionCampagne WHERE id_compte = ?";
+ try (PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, idCompte);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ int niveau = rs.getInt("niveau_debloque_max");
+ if (niveau < 1) return 1;
+ return Math.min(100, niveau);
+ }
+ }
+ } catch (SQLException e) {
+ System.err.println("Erreur lecture progression campagne : " + e.getMessage());
+ }
+ return 1;
+ }
+
+ public void debloquerNiveauCampagne(int idCompte, int niveauTermine) {
+ if (conn == null || idCompte <= 0) return;
+
+ int niveauCible = Math.min(100, Math.max(1, niveauTermine + 1));
+ initialiserProgressionCampagne(idCompte);
+
+ String sql = "UPDATE ProgressionCampagne SET niveau_debloque_max = MAX(niveau_debloque_max, ?) WHERE id_compte = ?";
+ try (PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, niveauCible);
+ ps.setInt(2, idCompte);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ System.err.println("Erreur mise à jour progression campagne : " + e.getMessage());
+ }
+ }
+
public String getStatsParCompte(int idCompte) {
if (conn == null || idCompte <= 0) {
return "Aucune statistique disponible.";
@@ -207,5 +260,18 @@ public class DatabaseConnection {
}
return "Aucune statistique disponible.";
}
+
+ public String getStatsCampagneParCompte(int idCompte) {
+ if (conn == null || idCompte <= 0) {
+ return "Campagne : non disponible.";
+ }
+
+ int niveauMax = getNiveauDebloqueCampagne(idCompte);
+ int niveauxTermines = Math.max(0, niveauMax - 1);
+
+ return "Campagne\n"
+ + "Niveau max débloqué : " + niveauMax
+ + "\nNiveaux terminés : " + niveauxTermines;
+ }
}
diff --git a/linea/Jeu.db b/linea/Jeu.db
index 17e7c0e..329c596 100644
Binary files a/linea/Jeu.db and b/linea/Jeu.db differ
diff --git a/linea/Jeu.java b/linea/Jeu.java
index e286a81..dce9290 100644
--- a/linea/Jeu.java
+++ b/linea/Jeu.java
@@ -29,9 +29,11 @@ public class Jeu implements KeyListener, ActionListener {
private int _niv;
protected Timer horloge;
+ protected JFrame fenetre;
protected double score = 0;
protected JLabel labScore;
protected JLabel labMeilleurScore;
+ protected JLabel labNiveau;
private DatabaseConnection db;
private int idCompte;
private int meilleurSansCompte = 0;
@@ -39,6 +41,7 @@ public class Jeu implements KeyListener, ActionListener {
private int tempsSansCompteSec = 0;
private long debutPartieMs = 0;
private boolean immortel = false;
+ private final boolean modeCampagne;
private int meilleurActuel() {
return idCompte > 0 ? db.getMeilleurScoreParCompte(idCompte) : meilleurSansCompte;
@@ -46,11 +49,11 @@ public class Jeu implements KeyListener, ActionListener {
private String statsActuelles() {
if (idCompte > 0) {
- return db.getStatsParCompte(idCompte);
+ return db.getStatsParCompte(idCompte) + "\n\n" + db.getStatsCampagneParCompte(idCompte);
}
- return "Nombre de morts : " + mortsSansCompte
- + "\nTemps de jeu total : " + tempsSansCompteSec + " s"
- + "\nMeilleur score : " + meilleurSansCompte;
+ return "💀 Nombre de morts: " + mortsSansCompte
+ + "\n⏱️ Temps total: " + tempsSansCompteSec + "s"
+ + "\n🏆 Meilleur score: " + meilleurSansCompte;
}
private void enregistrerPartie(int scoreActuel, int tempsPartieSec) {
@@ -67,26 +70,173 @@ public class Jeu implements KeyListener, ActionListener {
// CONSTRUCTEUR
//-------------------------------------------------------------------------
public Jeu(DatabaseConnection db, int idCompte, int niveau) {
+ this(db, idCompte, niveau, false);
+ }
+
+ public Jeu(DatabaseConnection db, int idCompte, int niveau, boolean modeCampagne) {
this.db = db;
this.idCompte = idCompte;
this._niv = niveau;
+ this.modeCampagne = modeCampagne;
labScore = new JLabel();
labScore.setText("
score : 0
");
labMeilleurScore = new JLabel();
+ labNiveau = new JLabel();
JPanel panneauScores = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0));
panneauScores.setOpaque(false);
panneauScores.add(labScore);
panneauScores.add(labMeilleurScore);
+ panneauScores.add(labNiveau);
ecran.add(panneauScores, BorderLayout.NORTH);
rafraichirMeilleurScore();
+ rafraichirNiveau();
}
private void rafraichirMeilleurScore() {
labMeilleurScore.setText("meilleur : " + meilleurActuel() + "
");
}
+ private void rafraichirNiveau() {
+ labNiveau.setText("niveau : " + _niv + "
");
+ }
+
+ private void rafraichirTitreFenetre() {
+ if (fenetre == null) {
+ return;
+ }
+ String titre = modeCampagne ? "Linea Game [CAMPAGNE]" : "Linea Game";
+ fenetre.setTitle(titre + (immortel ? " [MODE IMMORTEL 🛡️]" : ""));
+ }
+
+ private String categorieNiveau(int niveau) {
+ if (niveau <= 2) return "Facile";
+ if (niveau <= 4) return "Moyen";
+ if (niveau <= 6) return "Difficile";
+ if (niveau <= 8) return "Tres Difficile";
+ if (niveau <= 10) return "Expert";
+ if (niveau <= 12) return "Cauchemar";
+ if (niveau <= 14) return "Chaos";
+ if (niveau <= 16) return "Infernal";
+ if (niveau <= 18) return "Apocalypse";
+ if (niveau <= 20) return "Extreme";
+ if (niveau <= 30) return "Infini";
+ if (niveau <= 50) return "Au-dela";
+ return "Cosmos";
+ }
+
+ private void appliquerThemeNiveau() {
+ String categorie = categorieNiveau(_niv);
+ Color fond;
+ Color couleurLigne;
+ Color couleurAvant;
+ Color couleurArriere;
+ Color couleurTexte;
+
+ switch (categorie) {
+ case "Facile":
+ fond = new Color(225, 245, 225);
+ couleurLigne = new Color(45, 120, 45);
+ couleurAvant = new Color(55, 185, 80);
+ couleurArriere = new Color(175, 70, 70);
+ couleurTexte = new Color(20, 50, 20);
+ break;
+ case "Moyen":
+ fond = new Color(215, 235, 250);
+ couleurLigne = new Color(40, 95, 150);
+ couleurAvant = new Color(50, 165, 120);
+ couleurArriere = new Color(185, 80, 85);
+ couleurTexte = new Color(20, 45, 80);
+ break;
+ case "Difficile":
+ fond = new Color(250, 235, 200);
+ couleurLigne = new Color(155, 95, 40);
+ couleurAvant = new Color(195, 145, 55);
+ couleurArriere = new Color(175, 70, 40);
+ couleurTexte = new Color(85, 55, 20);
+ break;
+ case "Tres Difficile":
+ fond = new Color(245, 215, 195);
+ couleurLigne = new Color(145, 70, 40);
+ couleurAvant = new Color(185, 110, 65);
+ couleurArriere = new Color(175, 55, 45);
+ couleurTexte = new Color(80, 35, 25);
+ break;
+ case "Expert":
+ fond = new Color(210, 190, 235);
+ couleurLigne = new Color(105, 55, 160);
+ couleurAvant = new Color(130, 85, 200);
+ couleurArriere = new Color(170, 60, 95);
+ couleurTexte = new Color(45, 20, 70);
+ break;
+ case "Cauchemar":
+ fond = new Color(175, 160, 210);
+ couleurLigne = new Color(85, 65, 150);
+ couleurAvant = new Color(120, 90, 190);
+ couleurArriere = new Color(155, 55, 110);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Chaos":
+ fond = new Color(205, 120, 120);
+ couleurLigne = new Color(170, 40, 45);
+ couleurAvant = new Color(225, 115, 40);
+ couleurArriere = new Color(200, 30, 60);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Infernal":
+ fond = new Color(165, 70, 55);
+ couleurLigne = new Color(190, 35, 25);
+ couleurAvant = new Color(235, 95, 25);
+ couleurArriere = new Color(210, 25, 20);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Apocalypse":
+ fond = new Color(120, 40, 45);
+ couleurLigne = new Color(210, 30, 40);
+ couleurAvant = new Color(245, 95, 35);
+ couleurArriere = new Color(225, 20, 35);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Extreme":
+ fond = new Color(70, 70, 70);
+ couleurLigne = new Color(225, 225, 225);
+ couleurAvant = new Color(255, 235, 70);
+ couleurArriere = new Color(240, 80, 80);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Infini":
+ fond = new Color(35, 55, 85);
+ couleurLigne = new Color(95, 170, 255);
+ couleurAvant = new Color(70, 220, 255);
+ couleurArriere = new Color(180, 80, 255);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ case "Au-dela":
+ fond = new Color(20, 30, 70);
+ couleurLigne = new Color(120, 155, 255);
+ couleurAvant = new Color(80, 205, 255);
+ couleurArriere = new Color(205, 95, 255);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ default:
+ fond = new Color(10, 15, 45);
+ couleurLigne = new Color(140, 120, 255);
+ couleurAvant = new Color(120, 225, 255);
+ couleurArriere = new Color(235, 95, 255);
+ couleurTexte = new Color(245, 245, 245);
+ break;
+ }
+
+ ecran.setCouleurFond(fond);
+ ligne.setCouleurLigne(couleurLigne);
+ demiCercleAvant.setCouleur(couleurAvant);
+ demiCercleArriere.setCouleur(couleurArriere);
+ labScore.setForeground(couleurTexte);
+ labMeilleurScore.setForeground(couleurTexte);
+ labNiveau.setForeground(couleurTexte);
+ }
+
private boolean choisirNouveauCompte() {
List pseudos = new ArrayList<>(db.getPseudos());
pseudos.add(0, "Sans compte");
@@ -173,7 +323,7 @@ public class Jeu implements KeyListener, ActionListener {
return "🌌 Niveau " + niveau + " - Cosmos";
}
}
-
+
private void choisirModeImmortel() {
int result = JOptionPane.showConfirmDialog(
null,
@@ -202,15 +352,24 @@ public class Jeu implements KeyListener, ActionListener {
demiCercleAvant = new Cercle(90, -180);
demiCercleArriere = new Cercle(90, 180);
ligne = new Ligne(_niv);
+ ecran.setNiveau(_niv);
- // 3. Configuration visuelle
- demiCercleArriere.setCouleur(new Color(0.8f, 0.0f, 0.0f));
- demiCercleAvant.setCouleur(new Color(0.0f, 0.8f, 0.0f));
+ // 3. En mode immortel, positionner les cercles loin (la force les attirera vers la ligne)
+ // En mode normal, ils restent au centre (y = 200 par défaut du constructeur)
+ if (immortel) {
+ demiCercleAvant.y = -300;
+ demiCercleArriere.y = 300;
+ }
- // 4. Ajout à l'écran (l'ordre définit la superposition)
+ // 4. Configuration visuelle
+ appliquerThemeNiveau();
+
+ // 5. Ajout à l'écran (l'ordre définit la superposition)
ecran.ajouterObjet(demiCercleArriere);
ecran.ajouterObjet(ligne);
ecran.ajouterObjet(demiCercleAvant);
+
+ rafraichirNiveau();
}
public void demarrer() {
@@ -220,7 +379,8 @@ public class Jeu implements KeyListener, ActionListener {
// Passer le flag immortel à l'écran
ecran.setImmortel(immortel);
- JFrame fenetre = new JFrame("Linea Game" + (immortel ? " [MODE IMMORTEL 🛡️]" : ""));
+ fenetre = new JFrame();
+ rafraichirTitreFenetre();
// Initialise les objets une première fois
initialiserPartie();
@@ -243,12 +403,10 @@ public class Jeu implements KeyListener, ActionListener {
// Réinitialisation des variables de jeu
score = 0;
labScore.setText("score : 0
");
-
- // Demander le mode immortel à chaque reset
- choisirModeImmortel();
// Passer le flag immortel à l'écran
ecran.setImmortel(immortel);
+ rafraichirTitreFenetre();
// Ré-initialisation des objets graphiques
initialiserPartie();
@@ -271,6 +429,7 @@ public class Jeu implements KeyListener, ActionListener {
int bonus = ecran.getBonusRecupere();
if (bonus != 0) {
ecran.reinitialiserBonus();
+ int niveauActuel = _niv;
int nouveauNiveau = _niv + bonus;
// Limiter le niveau entre 1 et 100
@@ -279,6 +438,10 @@ public class Jeu implements KeyListener, ActionListener {
} else if (nouveauNiveau > 100) {
nouveauNiveau = 100;
}
+
+ if (modeCampagne && idCompte > 0 && bonus > 0) {
+ db.debloquerNiveauCampagne(idCompte, niveauActuel);
+ }
_niv = nouveauNiveau;
resetLevel();
@@ -295,18 +458,46 @@ public class Jeu implements KeyListener, ActionListener {
rafraichirMeilleurScore();
while (true) {
- Object[] options = {"Relancer", "Changer de compte", "Changer de niveau", "Voir stats", "Quitter"};
+ Object[] options = modeCampagne
+ ? new Object[]{"▶️ Relancer", "📊 Statistiques", "❌ Quitter"}
+ : new Object[]{"▶️ Relancer", "👤 Compte", "📈 Niveau", "📊 Statistiques", "❌ Quitter"};
+ String message = "🏁 GAME OVER\n\n"
+ + "Score: " + scoreActuel + "\n"
+ + "Meilleur: " + meilleurActuel() + "\n"
+ + "Durée: " + tempsPartieSec + "s";
+
int choix = JOptionPane.showOptionDialog(null,
- "Perdu\nScore : " + scoreActuel + "\nMeilleur : " + meilleurActuel(),
- "Game Over",
+ message,
+ "🏁 Fin de Partie",
JOptionPane.DEFAULT_OPTION,
- JOptionPane.INFORMATION_MESSAGE,
+ JOptionPane.PLAIN_MESSAGE,
null, options, options[0]);
if (choix == 0) {
+ int modeRelance = JOptionPane.showConfirmDialog(
+ null,
+ "Relancer en mode immortel ?",
+ "Relancer",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE
+ );
+
+ if (modeRelance == JOptionPane.CANCEL_OPTION || modeRelance == JOptionPane.CLOSED_OPTION) {
+ continue;
+ }
+
+ immortel = (modeRelance == JOptionPane.YES_OPTION);
+ ecran.setImmortel(immortel);
resetLevel();
break;
}
+ if (modeCampagne) {
+ if (choix == 1) {
+ JOptionPane.showMessageDialog(null, statsActuelles(), "📊 Statistiques", JOptionPane.INFORMATION_MESSAGE);
+ continue;
+ }
+ System.exit(0);
+ }
if (choix == 1) {
if (choisirNouveauCompte()) {
resetLevel();
@@ -324,7 +515,7 @@ public class Jeu implements KeyListener, ActionListener {
continue;
}
if (choix == 3) {
- JOptionPane.showMessageDialog(null, statsActuelles(), "Statistiques", JOptionPane.INFORMATION_MESSAGE);
+ JOptionPane.showMessageDialog(null, statsActuelles(), "📊 Statistiques", JOptionPane.INFORMATION_MESSAGE);
continue;
}
System.exit(0);
diff --git a/linea/Ligne.java b/linea/Ligne.java
index e81d3c3..04fb297 100644
--- a/linea/Ligne.java
+++ b/linea/Ligne.java
@@ -12,11 +12,11 @@ public class Ligne extends ObjetGraphique{
private double xCercle = 400;
private Segment SegCourant;
// vitesse de déplacement (augmente légèrement chaque frame)
- private double vitesse = 5.0;
+ private double vitesse = 4.8;
// croissance initiale (fractionnelle) appliquée chaque frame
- private double croissance = 0.001; // ~0.1% initial
+ private double croissance = 0.0008; // montée plus progressive
// facteur qui amplifie la croissance elle-même (pour accélérer la montée)
- private double facteurCroissance = 1.00003; // croissance augmente légèrement
+ private double facteurCroissance = 1.00002; // accélération plus douce
private int niveau = 1; // niveau de difficulté, à augmenter pour rendre le jeu plus difficile
@@ -95,50 +95,63 @@ public class Ligne extends ObjetGraphique{
}
// appliquer la croissance (vitesse *= 1 + croissance)
- vitesse *= (1.0 + croissance + (niveau * 0.00001)); // augmenter la croissance avec le niveau
+ vitesse *= (1.0 + croissance + (niveau * 0.000006)); // hausse niveau plus progressive
// augmenter légèrement la croissance pour que l'accélération s'amplifie
croissance *= facteurCroissance;
}
- // Indique si l'axe horizontal du cercle (cx) se trouve au niveau
- // d'un des segments de la ligne -> le cercle est "sur la ligne"
- public boolean estSurLaLigne(double cx) {
+ // Trouve le segment couvrant l'abscisse cx (une seule itération)
+ private Segment trouverSegmentAuX(double cx) {
for (Segment seg : segments) {
double x1 = seg.x;
double x2 = seg.x + seg.xLong;
- if ((cx >= Math.min(x1, x2)) && (cx <= Math.max(x1, x2))) {
- return true;
+ if (cx >= Math.min(x1, x2) && cx <= Math.max(x1, x2)) {
+ return seg;
}
}
- return false;
+ return null;
+ }
+
+ // Résultat de contact : null = pas sur la ligne, sinon double[]{ligneY, distance}
+ public double[] contactInfo(double cx, double cy, double rayon) {
+ Segment seg = trouverSegmentAuX(cx);
+ if (seg == null) return null;
+ double ligneY = (seg.xLong != 0) ? seg.y + ((cx - seg.x) / seg.xLong) * seg.yLong : seg.y;
+ double dist = pointSegmentDistance(cx, cy, seg.x, seg.y, seg.x + seg.xLong, seg.y + seg.yLong);
+ return new double[]{ligneY, dist};
+ }
+
+ // Indique si l'axe horizontal du cercle (cx) se trouve au niveau
+ // d'un des segments de la ligne -> le cercle est "sur la ligne"
+ public boolean estSurLaLigne(double cx) {
+ return trouverSegmentAuX(cx) != null;
}
// Vérifie la collision entre la ligne (segments) et un cercle
public boolean collisionAvec(Cercle c) {
- double cx = c.getX();
- double cy = c.getY();
- double rayon = c.getRayon();
-
- for (Segment seg : segments) {
- double x1 = seg.x;
- double y1 = seg.y;
- double x2 = seg.x + seg.xLong;
- double y2 = seg.y + seg.yLong;
-
- double dist = pointSegmentDistance(cx, cy, x1, y1, x2, y2);
- if (dist <= rayon) {
- return true;
- }
- }
- return false;
+ double[] info = contactInfo(c.getX(), c.getY(), c.getRayon());
+ return info != null && info[1] <= c.getRayon();
}
+ // Retourne la coordonnée Y de la ligne à l'abscisse cx
+ public double getYAuX(double cx) {
+ Segment seg = trouverSegmentAuX(cx);
+ if (seg == null || seg.xLong == 0) return 300;
+ return seg.y + ((cx - seg.x) / seg.xLong) * seg.yLong;
+ }
+
// Obtenir la vitesse actuelle de la ligne (pour les boules bonus)
public double getVitesse() {
return vitesse;
}
-
+
+ public void setCouleurLigne(Color couleur) {
+ for (Segment seg : segments) {
+ seg.setCouleur(couleur);
+ }
+ }
+
// distance minimale entre un point (px,py) et un segment (x1,y1)-(x2,y2)
private double pointSegmentDistance(double px, double py, double x1, double y1, double x2, double y2) {
double vx = x2 - x1;
diff --git a/linea/LineaAppli.java b/linea/LineaAppli.java
index d6356f5..33dac9c 100644
--- a/linea/LineaAppli.java
+++ b/linea/LineaAppli.java
@@ -1,13 +1,29 @@
package linea;
+import java.awt.GridLayout;
import java.util.List;
+import javax.swing.ButtonGroup;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
public class LineaAppli {
+ private static class SelectionJeu {
+ final int idCompte;
+ final boolean modeCampagne;
+
+ SelectionJeu(int idCompte, boolean modeCampagne) {
+ this.idCompte = idCompte;
+ this.modeCampagne = modeCampagne;
+ }
+ }
+
private static String choisirPseudo(DatabaseConnection db, String message, int messageType) {
List pseudos = db.getPseudos();
if (pseudos.isEmpty()) {
@@ -50,7 +66,38 @@ public class LineaAppli {
if (result == JOptionPane.OK_OPTION) {
return list.getSelectedIndex() + 1;
}
- return 1;
+ return -1;
+ }
+
+ private static int choisirNiveauCampagne(DatabaseConnection db, int idCompte) {
+ int niveauMaxDebloque = db.getNiveauDebloqueCampagne(idCompte);
+ if (niveauMaxDebloque < 1) {
+ niveauMaxDebloque = 1;
+ }
+
+ String[] niveaux = new String[niveauMaxDebloque];
+ for (int i = 1; i <= niveauMaxDebloque; i++) {
+ niveaux[i - 1] = genererLabelNiveau(i);
+ }
+
+ JList list = new JList<>(niveaux);
+ list.setSelectedIndex(niveauMaxDebloque - 1);
+ list.setVisibleRowCount(15);
+ JScrollPane scrollPane = new JScrollPane(list);
+ scrollPane.setPreferredSize(new java.awt.Dimension(350, 350));
+
+ int result = JOptionPane.showConfirmDialog(
+ null,
+ scrollPane,
+ "🏆 Campagne - Niveaux débloqués (1 à " + niveauMaxDebloque + ")",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE
+ );
+
+ if (result == JOptionPane.OK_OPTION) {
+ return list.getSelectedIndex() + 1;
+ }
+ return -1;
}
private static String genererLabelNiveau(int niveau) {
@@ -129,6 +176,63 @@ public class LineaAppli {
}
}
+ private static SelectionJeu choisirCompteEtModeRapide(DatabaseConnection db) {
+ while (true) {
+ List pseudos = db.getPseudos();
+
+ JPanel panel = new JPanel(new GridLayout(0, 1, 8, 8));
+ panel.add(new JLabel("Compte :"));
+
+ String[] optionsCompte = new String[pseudos.size() + 2];
+ optionsCompte[0] = "Sans compte";
+ for (int i = 0; i < pseudos.size(); i++) {
+ optionsCompte[i + 1] = pseudos.get(i);
+ }
+ optionsCompte[optionsCompte.length - 1] = "Gérer les comptes...";
+
+ JComboBox comboCompte = new JComboBox<>(optionsCompte);
+ panel.add(comboCompte);
+
+ panel.add(new JLabel("Mode :"));
+ JRadioButton modeClassique = new JRadioButton("Classique", true);
+ JRadioButton modeCampagne = new JRadioButton("Campagne");
+ ButtonGroup group = new ButtonGroup();
+ group.add(modeClassique);
+ group.add(modeCampagne);
+
+ JPanel panelMode = new JPanel(new GridLayout(1, 2, 8, 0));
+ panelMode.add(modeClassique);
+ panelMode.add(modeCampagne);
+ panel.add(panelMode);
+
+ int result = JOptionPane.showConfirmDialog(
+ null,
+ panel,
+ "Jouer rapidement",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE
+ );
+
+ if (result != JOptionPane.OK_OPTION) {
+ return null;
+ }
+
+ String choixCompte = (String) comboCompte.getSelectedItem();
+ if (choixCompte == null) {
+ return null;
+ }
+
+ if ("Gérer les comptes...".equals(choixCompte)) {
+ menuComptes(db);
+ continue;
+ }
+
+ int idCompte = "Sans compte".equals(choixCompte) ? -1 : db.getIdParPseudo(choixCompte);
+ boolean campagne = modeCampagne.isSelected();
+ return new SelectionJeu(idCompte, campagne);
+ }
+ }
+
//-------------------------------------------------------------------------
// Classe de base de l'application, rien à modifier ici
//-------------------------------------------------------------------------
@@ -140,31 +244,37 @@ public class LineaAppli {
db.createTables();
while (true) {
- Object[] options = {"Comptes", "Sans compte", "Quitter"};
- int choix = JOptionPane.showOptionDialog(null,
- "Choisissez une action :",
- "Menu", JOptionPane.DEFAULT_OPTION,
- JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
-
- if (choix == JOptionPane.CLOSED_OPTION || choix == 2) {
+ SelectionJeu selection = choisirCompteEtModeRapide(db);
+ if (selection == null) {
return;
}
- switch (choix) {
- case 1 -> {
- int niveau = choisirNiveau();
- new Jeu(db, -1, niveau).demarrer();
+ if (!selection.modeCampagne) {
+ int niveau = choisirNiveau();
+ if (niveau > 0) {
+ new Jeu(db, selection.idCompte, niveau).demarrer();
return;
}
- case 0 -> {
- Integer idCompte = menuComptes(db);
- if (idCompte != null) {
- int niveau = choisirNiveau();
- new Jeu(db, idCompte, niveau).demarrer();
- return;
- }
- }
+ continue;
}
+
+ if (selection.idCompte > 0) {
+ int niveau = choisirNiveauCampagne(db, selection.idCompte);
+ if (niveau > 0) {
+ new Jeu(db, selection.idCompte, niveau, true).demarrer();
+ return;
+ }
+ continue;
+ }
+
+ JOptionPane.showMessageDialog(
+ null,
+ "Campagne sans compte : progression non sauvegardée.",
+ "Campagne",
+ JOptionPane.INFORMATION_MESSAGE
+ );
+ new Jeu(db, -1, 1, true).demarrer();
+ return;
}
}
}
diff --git a/linea/ZoneDessin.java b/linea/ZoneDessin.java
index 9730a0b..571e990 100644
--- a/linea/ZoneDessin.java
+++ b/linea/ZoneDessin.java
@@ -29,7 +29,10 @@ public class ZoneDessin extends JPanel {
private ArrayList boolesBonus = new ArrayList();
// compteur pour générer les boules à intervalle régulier
private int compteurBoule = 0;
- private int intervalleBoule = 80; // générer une boule tous les 80 frames
+ private int intervalleBouleBase = 165; // moins de boules sur la durée
+ private int delaiInitialBoule = 97; // apparition initiale avancée de 3%
+ private int niveauActuel = 1;
+ private Double dernierJoueurY = null;
// type de bonus récupéré (-1 = rouge, 1 = vert, 0 = aucun)
private int bonusRecupere = 0;
@@ -38,10 +41,24 @@ public class ZoneDessin extends JPanel {
setPreferredSize(new Dimension(800, 600));
setBackground(new Color(220,170,0));
}
+
+ public void setCouleurFond(Color couleurFond) {
+ setBackground(couleurFond);
+ }
public void setImmortel(boolean immortel) {
this.immortel = immortel;
}
+
+ public void setNiveau(int niveau) {
+ if (niveau < 1) {
+ niveauActuel = 1;
+ } else if (niveau > 100) {
+ niveauActuel = 100;
+ } else {
+ niveauActuel = niveau;
+ }
+ }
public int getBonusRecupere() {
return bonusRecupere;
@@ -68,6 +85,20 @@ public class ZoneDessin extends JPanel {
if (estArrete==true) {
return;
}
+
+ Cercle cercleReference = null;
+ for (ObjetGraphique obj : listeObjets) {
+ if (obj instanceof Cercle) {
+ cercleReference = (Cercle) obj;
+ break;
+ }
+ }
+ double joueurY = (cercleReference != null) ? cercleReference.getY() : 300;
+ double vitesseJoueurY = 0.0;
+ if (dernierJoueurY != null) {
+ vitesseJoueurY = joueurY - dernierJoueurY;
+ }
+ dernierJoueurY = joueurY;
// --- 0. Récupérer la ligne pour synchroniser les boules ---
Ligne ligneObjet = null;
@@ -79,67 +110,107 @@ public class ZoneDessin extends JPanel {
}
// --- 1. Générer une boule au bord droit sur la ligne ---
+ double progression = Math.min(1.0, (niveauActuel - 1) / 25.0);
+ int intervalleBoule = (int) Math.round(intervalleBouleBase - progression * 20.0); // ~165 -> 145
+ double probaVerteBase = 0.35 - progression * 0.06; // ~35% -> 29%
+ double variationAleatoire = (Math.random() - 0.5) * 0.10; // +/- 5%
+ double probaVerte = Math.max(0.22, Math.min(0.45, probaVerteBase + variationAleatoire));
+
compteurBoule++;
if (compteurBoule >= intervalleBoule && ligneObjet != null) {
compteurBoule = 0;
- boolean estVerte = Math.random() < 0.7;
-
- // On récupère le Y du dernier segment de la ligne (bord droit de l'écran)
- // Note: Si votre classe Ligne n'a pas de méthode pour avoir le Y à X=800,
- // utilisez une valeur proche de la fin de votre liste de segments.
- double spawnX = 800;
- double spawnY = 300; // Valeur par défaut
+ boolean estVerte = Math.random() < probaVerte;
+
+ double spawnX = 800;
+ double spawnY;
+ if (estVerte) {
+ // Vertes: proches de la trajectoire du joueur pour être récupérables.
+ double margeVerte = 95.0 + progression * 45.0;
+ spawnY = joueurY + (Math.random() * (2.0 * margeVerte)) - margeVerte;
+ } else {
+ // Rouges: visent davantage le joueur, mais démarrent avec une marge d'esquive.
+ double offset = 220 + progression * 20.0 + Math.random() * 150.0;
+ spawnY = joueurY + (Math.random() < 0.5 ? -offset : offset);
+ }
+
+ if (spawnY < 20) spawnY = 20;
+ if (spawnY > 580) spawnY = 580;
+
+ if (!estVerte) {
+ // Marge suffisante pour esquiver, sans rendre les rouges inoffensives.
+ double distanceMin = 125.0;
+ if (Math.abs(spawnY - joueurY) < distanceMin) {
+ if (spawnY >= joueurY) {
+ spawnY = Math.min(580, joueurY + distanceMin);
+ } else {
+ spawnY = Math.max(20, joueurY - distanceMin);
+ }
+ }
+ }
// On crée la boule au bord droit (800)
BouleBonus boule = new BouleBonus(spawnX, spawnY, estVerte);
-
- // On lui donne la vitesse actuelle de la ligne pour qu'elle suive le mouvement
- boule.setVitesse(ligneObjet.getVitesse());
boolesBonus.add(boule);
}
// --- 2. Animation des objets ---
for (ObjetGraphique obj : listeObjets) obj.Animer();
- // Mettre à jour la vitesse des boules (car la ligne accélère avec le temps)
+ // Mettre à jour et animer les boules (une seule fois par frame)
if (ligneObjet != null) {
for (BouleBonus boule : boolesBonus) {
- boule.setVitesse(ligneObjet.getVitesse());
- boule.Animer();
+ boule.animerAvecCible(ligneObjet.getVitesse(), joueurY, vitesseJoueurY);
}
}
- // Les boules bougent via leur propre Animer() qui ajuste leur vitesse
-
- for (BouleBonus boule : boolesBonus) {
- boule.Animer();
- }
-
- // 2. vérifier collisions entre une Ligne et un Cercle
- for (ObjetGraphique obj : listeObjets) {
- if (obj instanceof Ligne) {
- Ligne l = (Ligne) obj;
- for (ObjetGraphique other : listeObjets) {
- if (other instanceof Cercle) {
- Cercle c = (Cercle) other;
- // On commence à surveiller une fois que le centre du
- // cercle est au-dessus d'un segment (le cercle est "sur la ligne").
- if (l.estSurLaLigne(c.getX())) {
- hadBeenOnLine = true;
- // Si le cercle n'est plus en contact (distance > rayon)
- // alors le joueur perd (il doit maintenir le contact).
- if (!l.collisionAvec(c)) {
- // Ignorer la collision si on est en mode immortel
- if (!immortel) {
- collisionOccur = true;
- estArrete = true;
- break;
- }
- }
- }
+ // 2. vérifier collision entre la Ligne et le Cercle (une seule passe de segments)
+ if (ligneObjet != null && cercleReference != null) {
+ double[] info = ligneObjet.contactInfo(
+ cercleReference.getX(), cercleReference.getY(), cercleReference.getRayon());
+
+ boolean enContact = (info != null) && (info[1] <= cercleReference.getRayon());
+ double ligneY = (info != null) ? info[0] : ligneObjet.getYAuX(cercleReference.getX());
+
+ // Phase initiale en mode immortel : force d'attraction depuis le début
+ if (!hadBeenOnLine && immortel) {
+ double delta = ligneY - cercleReference.y;
+ cercleReference.vitesse += delta * 0.12;
+
+ // Marquer le premier contact
+ if (enContact) {
+ hadBeenOnLine = true;
+ }
+ } else if (hadBeenOnLine && immortel) {
+ // Phase maintenance : après le premier contact, maintenir le cercle sur la ligne
+ double rayon = cercleReference.getRayon();
+ double limite = ligneY - rayon;
+ double limiteBas = ligneY + rayon;
+
+ // Clamping : empêcher le cercle de sortir au-dessus
+ if (cercleReference.y < limite) {
+ cercleReference.y = limite;
+ if (cercleReference.vitesse < 0) cercleReference.vitesse *= -0.3;
+ }
+ // Clamping : empêcher le cercle de sortir en-dessous
+ else if (cercleReference.y > limiteBas) {
+ cercleReference.y = limiteBas;
+ if (cercleReference.vitesse > 0) cercleReference.vitesse *= -0.3;
+ }
+ } else if (info != null && !enContact && !immortel) {
+ // Mode normal : mort si hors contact
+ collisionOccur = true;
+ estArrete = true;
+ }
+
+ // Synchroniser tous les autres Cercle en mode immortel
+ if (immortel) {
+ for (ObjetGraphique obj : listeObjets) {
+ if (obj instanceof Cercle && obj != cercleReference) {
+ Cercle autre = (Cercle) obj;
+ autre.y = cercleReference.y;
+ autre.vitesse = cercleReference.vitesse;
}
}
- if (estArrete) break;
}
}
@@ -164,7 +235,7 @@ public class ZoneDessin extends JPanel {
//4. Détruire la boule si elle dépasse le joueur sans être touchée ---
// Le cercle est à X = 400. Si la boule est à X < 350, elle est "passée".
- if (!collision && boule.getX() < 350) {
+ if (!collision && boule.getX() < 320) {
boolesBonus.remove(i);
// On ne change pas bonusRecupere ici (donc rien ne se passe)
}
@@ -205,7 +276,8 @@ public class ZoneDessin extends JPanel {
collisionOccur = false;
hadBeenOnLine = false;
bonusRecupere = 0;
- compteurBoule = 0;
+ compteurBoule = -delaiInitialBoule;
+ dernierJoueurY = null;
repaint();
}
}
\ No newline at end of file