updateBoule/immortel

This commit is contained in:
2026-03-28 14:18:17 +01:00
parent 4e8e947ff5
commit 1e7f70ab6b
9 changed files with 679 additions and 147 deletions

BIN
Jeu.db

Binary file not shown.

View File

@@ -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
@@ -59,6 +71,64 @@ public class BouleBonus extends ObjetGraphique {
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) {
double cx = c.getX();
@@ -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) {

View File

@@ -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"
double gravite = 0.95;
double poussee = 1.45;
double amortissement = 0.92;
// chute libre
vitesse = vitesse + 9.81 * pas;
// impulsion
if (montee==true) {
vitesse = vitesse - impulsion *pas;
vitesse -= poussee;
} else {
vitesse += gravite;
}
depY = 1/2 * 9.81 + vitesse * pas;
// Lissage global pour un ressenti plus régulier.
vitesse *= amortissement;
if (depY<-10) {
depY=-10;
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;
}
}

View File

@@ -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;
}
}

Binary file not shown.

View File

@@ -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("<html><h3>score : 0</h3></html>");
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("<html><h3>meilleur : " + meilleurActuel() + "</h3></html>");
}
private void rafraichirNiveau() {
labNiveau.setText("<html><h3>niveau : " + _niv + "</h3></html>");
}
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<String> pseudos = new ArrayList<>(db.getPseudos());
pseudos.add(0, "Sans compte");
@@ -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();
@@ -244,11 +404,9 @@ public class Jeu implements KeyListener, ActionListener {
score = 0;
labScore.setText("<html><h3>score : 0</h3></html>");
// 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
@@ -280,6 +439,10 @@ public class Jeu implements KeyListener, ActionListener {
nouveauNiveau = 100;
}
if (modeCampagne && idCompte > 0 && bonus > 0) {
db.debloquerNiveauCampagne(idCompte, niveauActuel);
}
_niv = nouveauNiveau;
resetLevel();
return;
@@ -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);

View File

@@ -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,43 +95,50 @@ 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();
double[] info = contactInfo(c.getX(), c.getY(), c.getRayon());
return info != null && info[1] <= 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;
// 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)
@@ -139,6 +146,12 @@ public class Ligne extends ObjetGraphique{
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;

View File

@@ -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<String> 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<String> 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<String> 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<String> 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;
}
}
}

View File

@@ -29,7 +29,10 @@ public class ZoneDessin extends JPanel {
private ArrayList<BouleBonus> boolesBonus = new ArrayList<BouleBonus>();
// 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;
@@ -39,10 +42,24 @@ public class ZoneDessin extends JPanel {
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;
}
@@ -69,6 +86,20 @@ public class ZoneDessin extends JPanel {
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;
for (ObjetGraphique obj : listeObjets) {
@@ -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;
boolean estVerte = Math.random() < probaVerte;
// 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
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
// 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());
for (BouleBonus boule : boolesBonus) {
boule.Animer();
}
boolean enContact = (info != null) && (info[1] <= cercleReference.getRayon());
double ligneY = (info != null) ? info[0] : ligneObjet.getYAuX(cercleReference.getX());
// 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;
}
}
}
// 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();
}
}